大致思路:
其实本质是“求子序列的个数”。很明显,这种问个数的,一般用动态规划来做。
这道题和“求最长公共子序列”(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];
}
};