不同子序列
题目链接:不同的子序列
题目描述:
因为需要前字符匹配的状态,所以我们考虑可以使用动态规划
状态是一维数组还是二维数组?
使用一维数组还是二维数组视情况而定。
题目所求是字符串S有几个子序列和T匹配,可能有人就认为只需要记录分解S就好了,但是如果只用S的子序列去匹配T,那么就要求子序列的长度必须大于等于T的长度,这是无法规划,无法定义出状态的。
所以我们尝试将用S的子序列去匹配T的子序列
状态定义为:S的 i 个字符匹配T的 j 个字符
所以我们使用二维数组 F(i ,j)
状态转移
状态:S的前 i 个字符和T的前 j 个字符的匹配个数
要匹配成功,S的第 i 个字符和T的第 j 个字符就必须一样
当S[ i ] == T[ j ]时,那么只要S的前 i-1 个字符和T的前 j-1 个字符也可以匹配就好
但是也存在像 S = “rabbb”,T = “rabb”,虽然S[ 5 ] ==T[ 4 ],但是使用S的最后一个字符一样可以匹配成功
所以 S[ i ] ==T[ j ]有两种情况:使用S的最后一个字符 or 不使用
因为是求次数,所以两种情况相加
当S [ i ] != T [ j ]时,就缩小问题,看S的前 i-1 个字符和T的前 j-1 个字符有多少匹配
所以状态转移方程:
S[ i ] ==T[ j ],F(i,j) = F(i-1,j-1) + F(i-1,j)
S[ i ] !=T[ j ], F(i,j) = F(i-1,j)
初始状态
若 i=0时,代表S的空串去匹配T的子串,不管T是什么,都是可以匹配成功的,所以F(i,0)=1
而 j=0时,代表S的子串去匹配T的空串,则无法匹配,所以F(0,j)=0
代码
int numDistinct(string S, string T)
{
// write code here
int row=S.size();
int col=T.size();
vector<vector<int>>dp(row+1,vector<int>(col+1,0));
dp[0][0]=1;
for(int i=1;i<=row;++i)
{
dp[i][0]=1;
for(int j=1;j<=col;++j)
{
//最后一个字符相同
//dp[i-1][j-1]是使用匹配的最后一个字符
//dp[i-1][j]是不使用匹配的最后一个字符
if(S[i-1]==T[j-1])
dp[i][j]=dp[i-1][j-1]+dp[i-1][j];
else//不相同,转化成子问题S的i-1个字符匹配T的j个字符
dp[i][j]=dp[i-1][j];
}
}
return dp[row][col];
}
空间优化
但是在实际使用中,我们发现二维数组的数据其实每次只用到当前行的上一行,所以我们可以将其优化成一维数组,但是 j 循环的起始应是最右边,从右往左更新,才不会提前覆盖所需数据
代码如下:
//一维数组
int numDistinct(string S, string T)
{
// write code here
int row=S.size();
int col=T.size();
vector<int>dp(col+1,0);
dp[0]=1;
for(int i=1;i<=row;++i)
{
//从右往左更新
for(int j=col;j>=0;--j)
{
if(S[i-1]==T[j-1])
dp[j]=dp[j-1]+dp[j];
//else删除行后变成 dp[j]=dp[j],所以省略
}
}
return dp[col];
}