118 不同的子序列(动态规划)与 77 最长公共子序列类似

给出字符串S和字符串T,计算S的不同的子序列中T出现的个数。

子序列字符串是原始字符串通过删除一些(或零个)产生的一个新的字符串,并且对剩下的字符的相对位置没有影响。(比如,“ACE”“ABCDE”的子序列字符串,而“AEC”不是)。 

样例

给出S = "rabbbit", T = "rabbit"

返回 3


分析:一般来说,如果题目里面给出两个字符串,基本是两种思路,一种就是递归判断,一种就是动态规划 


  • 从小到大的方式进行存储。i=1,j=1,dp[0][0] = 1;这里对第一列上面所有数字设为1,(因为当t为空的时候,永远都是只有一种方法变为空串);然后第一行全为0,因为当S为空,肯定是不行的,所以为0.
    动态规划一般的方法都是利用二维数组来进行每一层的计算(特殊处理方式都是在第一行第一列),最终得出对角线上的数值。
  • 我们从第一个字符开始进行比较。如果S[i-1] == T[j-1],那么表示dp[i][j] = dp[i-1][j-1] + dp[i-1][j];这个意思是两个字符相同的话,我们可以将他们舍去也可以将他们保留,这样的话前面的dp[i-1][j-1]中序列称为了新的序列,再加上dp[i-1][j]的序列,所以总的序列就是这个值
  • 如果S[i-1] != T[j-1] 那么说明dp[i][j] =  dp[i-1][j];这个意思是如果两个字符不同的话,那么我们就该舍弃,那么对应的就等于dp[i-1][j],前面该是多少是多少,i,j加入进来不会增加任何的变化。
  • 实例图

详细解释了状态转移方程。

这里我们用动态规划建立二维表格record,其中,record[i][j]表示字符串S的前i个字符构成的序列的子序列中,字符串T的前j项构成的序列的个数。对于这样的动态规划问题,难点就在于建立问题与其子问题之间的关系,也就是状态转移方程。

那现在来观察规律,我们不妨先将题意换个说法,与其说S的不同子序列中T出现的次数,不如说将S通过删除字符的方法转换成T的方法有几种。比如,样例中,S = "rabbbit",中间有3个"b",我们删除其中任何一个,都能得到 T = "rabbit"。这一点题目中其实也说了:“子序列字符串是原始字符串通过删除一些(或零个)产生的一个新的字符串”。


明白了这一点,我们就可以写出状态转移方程了。record[i][j]表示的是,S的前 i 项,通过删除一些字符,得到T的前 j 项,所用的方法种数。那么,当S由前 i - 1项升级到前 i 项的关键就在于多了一个S[i]。


如果S[i] != T[j],那这意味着什么?意味着我要从S的前 i 项通过删除变换到T的前 j 项就必须要删除S[i],因此S的前 i - 1项通过某些方法删除字符,得到T的前 j 项,增加了S[i] 之后,还用这些方法,只不过,每种方法中,还要增加一个删除S[i] 的操作。

举例说,S = "rabbc", T = "rab",当处理S的前4项“rabb”变换到T时(就假设j = 3,我们从此处开始把T的前 j 项就表述成T了),我们知道有两种方法:

1. 删除"rabb"中的第1个b

2. 删除"rabb"中的第2个b

现在,S升级为"rabbc"了,新增加的字符与T的最后一个字符不等,那么就还是刚才那两种方法,但是每一种方法添加一步删除"c"的操作。如下:

1. 删除"rabbc"中的第1个b,再删除最后的"c"

2. 删除"rabbc"中的第2个b,再删除最后的"c"

所以说,当S[i] != T[j]时,方法数与不含S[i]时是一样的:record[i][j] = record[i - 1][j]


而如果S[i] == T[j],情况就会稍微复杂一点。因为此时由S的前 i 项转换到T的前 j 项时,不一定非要删除S[i]了。我们将刚才那个例子变一下:S = "rabbb", T = "rab"

由S的前4项“rabb”变换到T时,有两种方法(和上面一样):

1. 删除"rabb"中的第1个b

2. 删除"rabb"中的第2个b

升级S,S = "rabbb",最后一个字符与T的最后一个字符相等,那就要分两种情况讨论了:

1. 删除S = "rabbb"的最后一个b,此时,与S的前4项变换到T的方法是一致的,就是我上面写的两种:删除"rabbb"的第1个或第2个"b"

2. 不删S = "rabbb"的最后一个b,此时,要使S变换成T,则需要让S的前 i - 1项变换成T的前 j - 1项,也就是"rabb"变换成"ra"。

总结一下,S = "rabbb"通过删除字符变换到T = "rab",一共三种方法:

1. 保留最后一个"b"时,删除第1个和第2个"b"(1种)record[i-1[j-1]

2. 不保留最后一个"b"时,删除第1个和第3个"b",或者删除第2个和第3个"b"    record[i-1][j]

所以状态转移方程为:record[i][j] = record[i - 1][j] + record[i - 1][j - 1]

可知,状态转移方程分两种情况,有两个。

record[i][j] = record[i - 1][j] + record[i - 1][j - 1] (S[i] == T[j])

record[i][j] = record[i - 1][j] (S[i] != T[j])


代码:

class Solution {
public:
    /*
     * @param : A string
     * @param : A string
     * @return: Count the number of distinct subsequences
     */
    int numDistinct(string S, string T) {
        // write your code here
        int m=S.size();
        int n=T.size();
        if(m==0)
        return 0;
       //m行n列:行表示S,列表示T:dp数组用来存储S的前i T的前j个字符可以有多少个可行的序列。
        vector<vector<int> >dp(m,vector<int>(n,0));
        for(int i=1;i<n;i++)
        dp[0][i]=0;  //前0个S串中含有子串T的个数肯定是0啊
        for(int j=0;j<m;j++)
        dp[j][0]=1;// 这个表示任意的一个字符串变成空串只有一种方法
        
        for(int j=1;j<n;j++)
           for(int i=1;i<m;i++)
           {
            if(S[i]==T[j])
            dp[i][j]=dp[i-1][j]+dp[i-1][j-1];
            else 
            dp[i][j]=dp[i-1][j];
           }
           
           return dp[m-1][n-1];
    }
};

转自: https://blog.csdn.net/guoziqing506/article/details/51681087

https://www.cnblogs.com/Kobe10/p/6340649.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值