• 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];
        }
    };
  • 相关阅读:
    洛谷 P2197 nim游戏
    洛谷 P1168 中位数
    第十一次发博不知道用什么标题好
    第十次发博不知道用什么标题好
    第九次发博不知道用什么标题好
    第八次发博不知道用什么标题好
    第七次发博不知道用什么标题好
    第六次发博不知道用什么标题好
    第五次发博不知道用什么标题好
    第四次发博不知道用什么标题好
  • 原文地址:https://www.cnblogs.com/xiangqi/p/14550949.html
Copyright © 2020-2023  润新知