LeetCode:115. 不同的子序列 动态规划

题目描述

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

一个字符串的一个子序列是指,通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新字符串。(例如,“ACE” 是 “ABCDE” 的一个子序列,而 “AEC” 不是)

示例 1:

输入: S = "rabbbit", T = "rabbit"
输出: 3
解释:

如下图所示,3 种可以从 S 中得到 "rabbit" 的方案。
(上箭头符号 ^ 表示选取的字母)

rabbbit
^^^^ ^^
rabbbit
^^ ^^^^
rabbbit
^^^ ^^^

示例 2:

输入: S = "babgbag", T = "bag"
输出: 5
解释:

如下图所示,5 种可以从 S 中得到 "bag" 的方案。 
(上箭头符号 ^ 表示选取的字母)

babgbag
^^ ^
babgbag
^^    ^
babgbag
^    ^^
babgbag
  ^  ^^
babgbag
    ^^^

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/distinct-subsequences
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

错误的思路

一开始是想先求最长相同子序列LCS,然后根据LCS的DP数组回溯找路径,试了下,有三分之一的样例超时
这里给出超时代码,听个响就好
在这里插入图片描述

class Solution {
public:
    int ans = 0;
    void dfs(vector<vector<int>>& dp, int x, int y, int len, string& s, string& t)
    {
        if(x<1 || y<1) return;
        if(len==0 && s[x]==t[1]) {ans++;}
        if(s[x]==t[y]) 
        {
            dfs(dp, x-1, y-1, len-1, s, t);
            if(dp[x][y]==dp[x-1][y]) dfs(dp, x-1, y, len, s, t);
            if(dp[x][y]==dp[x][y-1]) dfs(dp, x, y-1, len, s, t);
        }
        else if(dp[x-1][y]>dp[x][y-1]) dfs(dp, x-1, y, len, s, t);
        else if(dp[x-1][y]<dp[x][y-1]) dfs(dp, x, y-1, len, s, t);
        else if(dp[x-1][y]==dp[x][y-1]) 
        {
            dfs(dp, x-1, y, len, s, t); 
            dfs(dp, x, y-1, len, s, t);
        }
    }
    int numDistinct(string s, string t)
    {
        if(s.length()<t.length()) return 0;
        if(t.length()==0) return 1;
        s.insert(s.begin(), '#'); t.insert(t.begin(), '#');
        vector<vector<int>> dp(s.length()+1);
        for(int i=0; i<s.length(); i++) dp[i]=vector<int>(t.length()+1);
        for(int i=1; i<s.length(); i++)      
            for(int j=1; j<t.length(); j++)
                if(s[i]==t[j]) dp[i][j]=dp[i-1][j-1]+1;
                else dp[i][j]=max(dp[i-1][j], dp[i][j-1]);
        if(dp[s.length()-1][t.length()-1]!=t.length()-1) return 0;   
        dfs(dp, s.length()-1, t.length()-1, t.length()-2, s, t);
        return ans;
    }
};

正确的思路

不过正是因为自己一开始不知道干嘛求了个LCS,其实上面dfs的时候,是没怎么用到dp数组的值的,对于这个问题我们应该使用动态规划
在这里插入图片描述
观察路径我们发现:

  • 向左上方走,表示两个串局部匹配了,往前看
  • 往上方走,表示两个串不匹配,那么尝试跳过s串的部分字符,也就是向上走
  • 没有向左走,因为不允许我们跳过t串的字符

其实每次就是往上和往左了,那么我们往左上走的次数为t串的长度时,就说明我们找完了

走法:

  • 对于两串某位置相同,我们可以向左上走,也就是同时跳过相同的字符,也可以向上走,即选择跳过这个字符,因为前面还可能有相同的
  • 对于两串某位置不同,我们只能向上走,即即选择跳过这个字符,尝试在前方寻找匹配

或者再抽象一点,问题是在s串中删除一定数量的字符,使得s串变成t串,问一共多少种方法?

可以这么理解:向上走就是删除,向左上走就是匹配上了

那么状态转移就出来了:

约定:我们在s,t串的前头加上一个字符,把他看成空,这样方便处理空串的情况

dp[i][j]表示 【s串的1到i下标的子串】有多少种方法变为【t串的1到j下标的子串】

状态转移:
if(s[i]==t[j]) dp[i][j] = dp[i-1][j-1] + dp[i-1][j];
else if(s[i]!=t[j]) dp[i][j] = dp[i-1][j];

初始状态:

dp[任意][0] = 1; 表示任意长度的串变成空串只有一种方法,就是删光

代码

class Solution {
public:
    int numDistinct(string s, string t)
    {
        if(s.length()<t.length()) return 0;
        s.insert(s.begin(), '#'); t.insert(t.begin(), '#');
        long long dp[s.length()+1][t.length()+1]; memset(dp, 0, sizeof(dp));
        for(int i=0; i<s.length(); i++) dp[i][0]=1;
        for(int i=1; i<s.length(); i++)      
            for(int j=1; j<t.length(); j++)
                if(s[i]==t[j]) dp[i][j]=dp[i-1][j-1]+dp[i-1][j];
                else dp[i][j]=dp[i-1][j];
        return (int)dp[s.length()-1][t.length()-1];
    }
};
发布了238 篇原创文章 · 获赞 7 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_44176696/article/details/104780210