Leetcode每日一题 115. 不同的子序列

由于对于动归了解不够深入,以及没有用动归做过类似的字符串匹配题目,导致今天这道题花费了整整一个下午,外加看了别人说要用动归才想出来,害,真菜。

115. 不同的子序列

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

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

题目数据保证答案符合 32 位带符号整数范围。

示例 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
    ^^^

提示:

  • 0 <= s.length, t.length <= 1000
  • s 和 t 由英文字母组成

这种题,难度还是困难,递归暴力肯定是第一个排除的,想都不要去想。

就从这道题开始我的字符串动归的学习之路吧,首先是看到这道题思考,要如何去使用动态规划去保证你能够得到你想要的结果。

第一件事就是建立dp数组,一定要深刻理解dp数组中每一个元素的含义以及下标的含义。不然永远学不会。

那么怎么去建立呢,抱歉,我也想不到,不过得到大佬的提示还是知道了要建立一个二维数组,原因是 dp[i][j] 代表了 s字符串 0~i-1 中 包含了多少个 0~j-1 个t ,又是熟悉的边界坑,有人可能会问,为什么dp数组的下标里明明是i,j,却代表的是字符串中i-1和j-1啊之类的,那是因为,dp数组为了把子串为空的情况考虑进去,比如说 s = “bba” 匹配 t = "b"  首先要考虑的不是 s中的"b" 是否匹配t中的 "b" 而是考虑 s 中的空字符 " " 是否匹配 t中的空字符 " ",然后才是s中的 "b" 是否匹配空字符等等,看到这应该明白了吧,也就是说dp数组扩充了边界,加入了s与t的子串为空的情况,因为s与t中又没有真正的空串,所以判断s,t的时候下标要减一,才能对应上dp[i][j]。

先把动归的转移方程摆上来,方便下面的例子解释:

if(s[i - 1] == t[j - 1])
      dp[i][j] = dp[i-1][j-1] + dp[i-1][j];
else
      dp[i][j] = dp[i-1][j];

其实这道题不难,难点是你不知道这个dp数组为什么这样求就是正确答案,不明白转移方程的含义,无法把大问题分成子问题,迭代过程不清晰,所以就是很懵,我们先从这一点开始理解,就是先抛弃那些逻辑关系,我们已经知道dp[i][j]代表了 s字符串 0~i-1 中 包含了多少个 0~j-1 个t  对吧,意思就是dp[i][j] 就是一个解,一个答案,那么我们这样想,假如我们已经知道了"bagg" 里面有多少个"bag" ,是dp[4][3] = 2 ,实质题目是求"baggg"里面有多少个"bag",此时是不是就相当于往"bagg"后面多加了一个"g",对吧,那么我们略过t中的"b"与"ba",直接到判断"bag”这里来,此时s[i-1] = "g" ,t[i-1] = "g" , 相同,所以等于dp[i-1][j-1] + dp[i-1][j];为什么是这两个相加呢。想象一下,假设我们加入的是一个“?”字符,先不管它是什么,现在是不是变成了“bagg?” ,但是,在之前我们已经得知了“bagg”里面有多少个"bag",现在即使多了个字符,之前的答案是不是必须要加进来的,也就是dp[i-1][j],然后现在“g”进来了,它与"bag"最后一个字符串“g”相同,也就是说,我们可以拿它与最后一个“g”相匹配,也可以选择不与“g”相匹配,所以匹配与不匹配造成了两种结果,就是说"bag"可以用"ba”跟这个"g”组合成一个“bag” 它也可以跟另外两个"g”组合,意思就是因为进来了这个"g",之前所有的“ba”又可以和这个"g"组成一个新的"bag",把这个“g”考虑进来,就得到了dp[i-1][j-1],区别是j变成了j-1,因为“g”被用来匹配了,而不用“g”,就相当于进来了个“z”,变成了"baggz",那此时dp[i][j]的结果必然就是跟不加“z”之前一样的为dp[i-1][j],是一个道理。所以必须要用dp[i-1][j-1]+dp[i-1][j]才是正确答案。

所以思维方式必须是我们已知dp[i][j]的答案,然后判断下一个字符加进来时,会造成什么样的后果就行了,然后组成一个完整的字符串。

贴上代码观看:

class Solution {
public:
    int numDistinct(string s, string t) {
        int n = s.length();
        int m = t.length();

        vector<vector<long>> dp(n+1,vector<long>(m+1));
        for(int i = 0 ; i <= n ; i++)dp[i][0] = 1;
        for(int i = 1 ; i <= m ; i++)dp[0][i] = 0;

        for(int i = 1 ; i <= n ; i++)
        {
            for(int j = 1 ; j <= m ; j++)
            {
                if(s[i - 1] == t[j - 1])
                    dp[i][j] = dp[i-1][j-1] + dp[i-1][j];
                else
                    dp[i][j] = dp[i-1][j];
            }
        }
        
        
        return dp[n][m];
    }
};
原文地址:https://www.cnblogs.com/xiangqi/p/14550949.html