【动态规划】不同的子序列


话不多说,上题!

牛客链接

题目:给定两个字符串S和T,返回S子序列等于T的不同子序列个数有多少个?

字符串的子序列是由原来的字符串删除一些字符(也可以不删除)在不改变相对位置的情况下的剩余字符(例如,"ACE"is a subsequence of"ABCDE"但是"AEC"不是)
例如:

S=“nowcccoder”, T = “nowccoder”
返回3

一、题意分析

字符串子序列概念:

根据题目所述,字符串的子序列就是在不改变字符的位置的情况下,对字符进行部分删除(也可以不删除)而得到的字符串就是该字符串的子序列

例如:字符串“abc”的子序列就有“a”,“b”,“c”,“ab”,“ac”,“bc”,“abc”。但是“ba”这样的字符串就不是原来的字符串的子序列,因为改变了字符的相对位置。

需求分析:

有俩字符串S和T,要求的就是S的子序列中有多少个子序列是和T字符串一模一样

例如:S=“nowcccoder”, T = “nowccoder”

想要满足题意,S的子序列可以是“now ccoder”(没用第一个c),“nowc coder”(没用第二个c),“nowcc oder”(没用第三个c),所以结果为3

二、思路分析

可以将子问题分成S的子串中与T相同的子序列的个数,那么在状态分析的时候,状态F(i)就应该是S的前i个字符构成的子串与T相同的子序列的个数,并且需要保证子串的长度比T字符串长,虽然看上去没什么问题,但是实际分析实例的时候就会出现问题

例如:S=“abaaa”,T=“aba”,显而易见,F(3) = 1,但当求F(4)时,我们知道S字符串中的第四个字符是“a”和T字符串最后一个字符相同,有被使用的价值,如果被使用的话,就意味着前面的字符串必须得是“ab”然后在拼接上第4个字符“a”,就意味着需要将S字符串的第三个字符“a”删除,使之和T字符串的前两个字符组成的子字符串相同。但是状态中并没有对S字符串的子串与T字符串前两个字符相同的子序列个数进行推导分析,不符合状态推导。

因此真正的状态应该改成S的前i个字符中与T的前j个字符相同的子序列的个数

  • 第i个字符和第j个字符相同时,说明该字符可以被使用。

    • 如果S中的第i个字符被使用了,那么需要在S字符串的前i-1个字符中找与T中前j-1个字符相同的子序列;
    • 如果没有被使用,那么需要在S字符串的前i-1个字符中找与T中前j个字符相同的子序列。
  • 若是第i个字符和第j个字符不相同时,那么该字符没有被使用的可能。

    • 只能从S字符串的前i-1个字符中找与T中前j个字符相同的子序列。

初始值设置就是S字符串前0个字符与T字符串前0个字符相同的子序列有1个

动态规划四个角度:

  1. 状态定义F(i,j) :S的前i个字符中与T的前j个字符相同的子序列的个数

  2. 状态间的转移方程定义

    • S[i] == T[j],如果使用S[i] ,F(i,j)= F(i-1,j-1);如果不使用,F(i,j)= F(i-1,j)。因此F(i,j)= F(i-1,j-1)+ F(i-1,j)
    • s[i] != T[j],F(i,j)= F(i-1,j)
  3. 状态的初始化:F(i,0)= 1 (表示S的子串与空串相同的个数,只有空串与空串相同)

  4. 返回结果F(S.length(),T.length())

三、代码结果

public int numDistinct (String S, String T) {
    
    
    int row = S.length();
    int col = T.length(); 
    int[][] num = new int[row + 1][col + 1];//存放状态
    num[0][0] = 1;
    for(int i = 1;i <= row;i ++) {
    
    
        num[i][0] = 1;//状态初始化
        for(int j = 1;j <= col;j ++) {
    
    
            if(S.charAt(i-1) == T.charAt(j-1)) {
    
    
                num[i][j] = num[i-1][j-1] + num[i-1][j];
                //S的第i个字符和T的第j个字符相同
            }else {
    
    
                num[i][j] = num[i-1][j];
                //S的第i个字符和T的第j个字符不相同
            }
        }
    }
    return num[row][col];//返回结果
}

四、代码升级

实际上,我们在求num[i][j](状态F(i,j))的时候只使用了num二维矩阵的第i-1行的数据,因此只需要使用一个一维数组(大小为T字符串的大小加一)进行存储数据即可,并且第二层循环一定要从后向前进行,如果从前向后,那么使用的数据即状态都是本行的而不是上一行的,会出现差错,关于这一点在01背包问题中也说过,这里就不多加解释。

01背包问题链接

public int numDistinct (String S, String T) {
    
    
    int row = S.length();
    int col = T.length(); 
    int[] num = new int[col + 1];//存放状态
    num[0] = 1;
    for(int i = 1;i <= row;i ++) {
    
    
        for(int j = col;j >= 1;j --) {
    
    
            if(S.charAt(i-1) == T.charAt(j-1)) {
    
    
                num[j] = num[j-1] + num[j];//状态转移方程
            }
        }
    }
    return num[col];//返回结果
}

完!

猜你喜欢

转载自blog.csdn.net/weixin_46103589/article/details/122398846