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
^^^
解题
动态规划——S的前i个字符表示T的前j个字符的子序列有多少种;
遍历S和T,若S【i】==T【j】,dp[i][j]= dp[i-1][j-1]+dp[i-1][j]; (使用i作为t子序列第j个字符+不使用i作为第j个字符)
若S【i】!=T【j】,dp[i][j]=dp[i-1][j],只能不适用S[i]作为子序列T的第j个字符。
初始化
dp【i】【0】为1; S中每个子串对空字符串都有一种表示方法;
二维动态规划
class Solution {
public:
int numDistinct(string s, string t) {
//s的前i个数可以的子序列 中等于t的前j个数的个数
int snum=s.size();
int tnum=t.size();
dp.resize(snum+1,vector<long long>(tnum+1,0));
for(int i=0;i<snum;i++)
dp[i][0]=1;
for(int j=1;j<=tnum;j++)
for(int i=1;i<=snum;i++){
if(s[i-1]==t[j-1]) dp[i][j]=dp[i-1][j]+dp[i-1][j-1];
//不将第i个字符放入子序列的个数+将第i哥字符放入子序列最后一个的个数
else dp[i][j]=dp[i-1][j];
//不相等则不用加
}
return (int)dp[snum][tnum];
}
private:
vector<vector<long long>> dp;
};
空间优化:滚动数组
改为滚动数组,j需要从后向前;
因为dp[j]的滚动用到【i-1】【j-1】,即上一层的j-1,故需保留j-1,从后往前滚动!
class Solution {
public:
int numDistinct(string s, string t) {
//s的前i个数可以的子序列 中等于t的前j个数的个数
int snum=s.size();
int tnum=t.size();
dp.resize(tnum+1,0);
for(int i=0;i<snum;i++)
dp[0]=1;
for(int i=1;i<=snum;i++)
for(int j=tnum;j>0;j--)
{
if(s[i-1]==t[j-1]) dp[j]=dp[j]+dp[j-1];
//不将第i个字符放入子序列的个数+将第i哥字符放入子序列最后一个的个数
//else dp[j]=dp[j];
//不相等则不用加
}
return (int)dp[tnum];
}
private:
vector<long long> dp;
};
总结
递推公式——由字符的比较得到dp的递推公式。