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<vector<int>> memo(m+1,vector<int>(n+1,-1));
        for(int i=0;i<m+1;i++)
            memo[i][n]= 0;//竖直边界
        for(int j=0;j<n+1;j++)
            memo[m][j]= 1; //横向边界,后算把前面的覆盖掉
        return traceback(s,t,0,0,memo);//从前往后
    }
    int traceback(string &s, string &t,int i,int j,vector<vector<int>> &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<vector<int>> memo(t.size(),vector<int>(s.size(),-1));
        return traceback(s,t,0,0,memo);//从前往后
    }
    int traceback(string &s, string &t,int i,int j,vector<vector<int>> &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<vector<int>> memo(t.size(),vector<int>(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<vector<int>> &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<vector<unsigned int>> dp(m+1,vector<unsigned int>(n+1,0));
        //for(int i=0;i<m+1;i++)
           //dp[i][0]= 0; //竖向边界
        for(int j=0;j<n+1;j++)
            dp[0][j]= 1; //横向边界,把前面的覆盖
        for(int i=1;i<=m;i++){
            for(int j=1;j<=n;j++){
                if(t[i-1]==s[j-1]) //这里需要左移比较,因为加了左边界,在dp中i表示第i个字符
                    dp[i][j] = dp[i-1][j-1] + dp[i][j-1];
                else dp[i][j] = dp[i][j-1];
            }}
        return dp[m][n];
    }
};

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

class Solution {
public:
    int numDistinct(string s, string t) {
        int m =t.size(); int n = s.size();
        vector<unsigned int> 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];
}
};
posted @ 2022-06-10 20:35  失控D大白兔  阅读(22)  评论(0编辑  收藏  举报