*【动态规划】求子序列的个数 distinct subsequences

大致思路:

其实本质是“求子序列的个数”。很明显,这种问个数的,一般用动态规划来做。

这道题和“求最长公共子序列”(https://blog.csdn.net/m0_38033475/article/details/79492786)有些类似。类似的地方在于:序列都是可以不连续的。不同的地方在于:最长公共子序列是两个序列都随便出,找相同序列的最长的长度;这道题是母串必须包含子串的全部,而且是问“个数/情况数”而非长度。

这两道题的对比是比较有意思的:

  • 最长公共子序列的做法:
for(int i=1;i<=s1.size();i++)
		{
			for(int j=1;j<=s2.size();j++)
			{
				if(s1[i]==s2[j]) 
					dp[i][j] = dp[i-1][j-1] + 1;//一定是这最后一个字母来作为公共子序列的结尾 
				else 
					dp[i][j] = max(dp[i][j-1],dp[i-1][j]);//一定不是它做结尾,则看之前的情况谁长谁上 
			}
		}
  • 本题,求子序列的个数:(注意要从“个数/情况”来思考,而非长度 !)

我们需要一个二维数组dp(i)(j)来记录长度为i的字串在长度为j的母串中出现的次数,这里长度都是从头算起的,而且遍历时,保持子串长度相同,先递增母串长度,母串最长时再增加一点子串长度重头开始计算母串。

首先我们先要初始化矩阵,当子串长度为0时,所有次数都是1,当母串长度为0时,所有次数都是0.当母串子串都是0长度时,次数是1(因为都是空,相等)。接着,如果子串的最后一个字母和母串的最后一个字母不同,说明新加的母串字母没有产生新的可能性,可以沿用该子串在较短母串的出现次数,所以dp(i)(j) = dp(i)(j-1)如果子串的最后一个字母和母串的最后一个字母相同,说明新加的母串字母带来了新的可能性,我们不仅算上dp(i)(j-1),也要算上新的可能性。那么如何计算新的可能性呢,其实就是在既没有最后这个母串字母也没有最后这个子串字母时,子串出现的次数,我们相当于为所有这些可能性都添加一个新的可能。所以,这时dp(i)(j) = dp(i)(j-1) + dp(i-1)(j-1)

我的理解:对于S[j]!=T[i]的不用说了,加了这个跟没加这个母串的新字符,情况数一样;而对于相等时,原来的情况数肯定是要考虑的,但还要新增大家都以母串新增的这个数作为公共序列的收尾,也就是说公共序列的最后一个数位置被定死在这了,所以是对于dp[i-1][j-1]的所有情况数(长度不一样,情况数一样!!)

编程注意:

dp数组的初始化很重要,对于刚开始的临界条件要想好,初始化好

②不管是求最长公共子序列还是这道题,由于涉及i-1这种,所以下标都是从1开始遍历的,之前是觉得说要让字符串也从1开始存,但其实只是在比较的时候比较T[i-1]和S[j-1]即可。

编程的时候一定要全神贯注!!之前写成S[i-1]找了好久bug没找到。。。。

AC代码:

class Solution {
public:
    int numDistinct(string S, string T)
    {
        int dp[2000][2000]; //dp[i][j]:i表示子串长度,j表示母串长度
        //dp数组初始化
        for(int i=0;i<2000;i++)
        {
            for(int j=0;j<2000;j++)
            {
                dp[i][j]=0;
                dp[0][j]=1; //子串长度为0,即母串删去所有字符,出现1次
            }
        }
        
        int len_t = T.size();
        int len_s = S.size();
        for(int i=1;i<=len_t;i++)
        {
            for(int j=1;j<=len_s;j++)
            {
                if(T[i-1]!=S[j-1])
                    dp[i][j] = dp[i][j-1];
                else
                    dp[i][j] = dp[i][j-1] + dp[i-1][j-1]; //新增最后一个共同字符都定死到母串最后一个新字符的可能情况数
            }
        }
        return dp[len_t][len_s];
    }
};

次日,做到一道很类似的题目,也是求方案的个数。

大致思路:

很快便想到了动态规划求方案数的做法,只针对当前新增的这个字符,可以单独表示一个单词(dp[i]+=dp[i-1])OR 与上一个结合(dp[i]+=dp[i-2])

但是仍然欠缺考虑一些特殊情况,比如:

  • 若新增的这个字符是'0',则不能单独表示一个单词,必须与上个结合。
  • 因为动态规划的需要设置dp[0]=1,但是如果真的字符串为空了,应该输出0. 并且,如果字符串是0打头,也是表示不了任何单词的,该输出0.

AC代码:

class Solution {
public:
    int numDecodings(string s) {
        if(s=="")
            return 0;
        if(s[0]=='0')
            return 0;
        long long dp[5000]={0};
        dp[0]=1;
        dp[1]=1;
        int len = s.size();
        for(int i=2;i<=len;i++)
        {
            if(s[i-1]>'0')
                dp[i] = dp[i-1]; //单个数字表示一个单词(还是有前提:不能为0)
            if((s[i-2]=='1' && s[i-1]<='9')||(s[i-2]=='2' && s[i-1]<='6')) //可以和前一个数字组合表示一个单词
            {
                dp[i] += dp[i-2];
            }
        }
        return dp[len];
    }
};

猜你喜欢

转载自blog.csdn.net/m0_38033475/article/details/92179093