LeetCode/不同的子序列


给定一个字符串 s 和一个字符串 t ,计算在 s 的子序列中 t 出现的个数

1. 递归

题目其实就是要求我们在s中从前往后挑选字符来匹配子序列,得到最后的匹配次数
也就是成功匹配的递归分支

class Solution {
public:
    int numDistinct(string s, string t) {
        return traceback(s,t,0,0);//从初始位置开始递归,从前往后
    }
    int traceback(string &s, string &t,int i,int j){
        if(i>t.size()) return 1;//base case,先判断该条件
        if(j>s.size()) return 0;//base case,s中字符选完
        if(t[i]==s[j]) return traceback(s,t,i+1,j+1)+traceback(s,t,i,j+1);//如果可以匹配,选择匹配和跳过两种分支
        else return traceback(s,t,i,j+1);//不能匹配跳过该字符
    }
};

2. 记忆化搜索

方法一递归的优化,减少重复递归

从前往后(带边界)
class Solution {
public:
    int numDistinct(string s, string t) {
        int m =t.size(); int n = s.size();
        vector> memo(m+1,vector(n+1,-1));
        for(int i=0;i> &memo){
        if(memo[i][j]!=-1) return memo[i][j];//跳过该字符
        if(t[i]==s[j])
             memo[i][j] = traceback(s,t,i+1,j+1,memo)+traceback(s,t,i,j+1,memo);
        else memo[i][j] = traceback(s,t,i,j+1,memo);
        return memo[i][j];
    }
};
从前往后
class Solution {
public:
    int numDistinct(string s, string t) {
        vector> memo(t.size(),vector(s.size(),-1));
        return traceback(s,t,0,0,memo);//从前往后
    }
    int traceback(string &s, string &t,int i,int j,vector> &memo){
        if(i>t.size()-1) return 1;//边界条件
        if(j>s.size()-1) return 0;//边界条件
        if(memo[i][j]!=-1) return memo[i][j];//跳过该字符
        if(t[i]==s[j])
             memo[i][j] = traceback(s,t,i+1,j+1,memo)+traceback(s,t,i,j+1,memo);
        else memo[i][j] = traceback(s,t,i,j+1,memo);
        return memo[i][j];
    }
};
从后往前
class Solution {
public:
    int numDistinct(string s, string t) {
        vector> memo(t.size(),vector(s.size(),-1));
        return traceback(s,t,t.size()-1,s.size()-1,memo);
    }
    int traceback(string &s, string &t,int i,int j,vector> &memo){
        if(i<0) return 1;
        if(j<0) return 0;
        if(memo[i][j]!=-1) return memo[i][j];
        if(t[i]==s[j])
             memo[i][j] = traceback(s,t,i-1,j-1,memo)+traceback(s,t,i,j-1,memo);
        else memo[i][j] = traceback(s,t,i,j-1,memo);
        return memo[i][j];
    }
};

3. 动态规划

考虑从前往后递推,与前面带边界递归基本一致
边界条件为dp[0][j]=1,dp[i][0]=0
状态转移方程

\[dp[i][j]=\begin{cases} dp[i-1][j-1]+dp[i][j-1], &t[i]==s[j]\\ dp[i][j-1], & t[i]!=s[j]\\ \end{cases} \]

class Solution {
public:
    int numDistinct(string s, string t) {
        int m =t.size(); int n = s.size();
        vector> dp(m+1,vector(n+1,0));
        //for(int i=0;i

4. 动态规划(一维优化)

class Solution {
public:
    int numDistinct(string s, string t) {
        int m =t.size(); int n = s.size();
        vector dp(m+1,0);
        dp[0]=1;
        for(int j=1;j<=n;j++)//从第一列开始
            for(int i=m;i>0;i--)//从下往上遍历,复用前一列上一行的数
                if(t[i-1]==s[j-1])
                    dp[i] = dp[i-1] + dp[i];
    return dp[m];
}
};