算法刷题打卡047 | 动态规划15

LeetCode 392 判断子序列

题目链接:392. 判断子序列 - 力扣(Leetcode)

leetcode上这道题是简单题,可以用双指针方法匹配两个字符串。要判断s是不是t的子序列,首先需要s的长度<=t的长度。子序列强调顺序,因此用两个指针i, j分别遍历两个字符串,只有在s[j] == t[i]时,指向s的指针j才向右移动,遍历t的指针则一直在移动,寻找与当前s[j]相等的字符。最后如果s可以遍历完,那么s就是t的子序列(t是否遍历完不影响结果)。总的时间复杂度是O(len(s)+len(t)),空间复杂度是O(1)。

class Solution:
    def isSubsequence(self, s: str, t: str) -> bool:
        ns, nt = len(s), len(t)
        if ns > nt:
            return False
        i, j = 0, 0
        while i < nt and j < ns:
            if s[j] == t[i]:
                j += 1
            i += 1
        return True if j == ns else False

若用动态规划的思路解题,使用二维dp数组,dp[i][j]表示以s[i-1]结尾的s和以t[j-1]结尾的t已经匹配的字符个数(即相同子序列的长度)。如果s[i-1]==t[j-1],相同子序列长度在dp[i-1][j-1]的基础上增加1;不相等时,t字符串的遍历要继续直到找到与当前s[i-1]相等的字符,那么当前已经匹配的字符个数需要沿用dp[i][j-1]的结果:

class Solution {
public:
    bool isSubsequence(string s, string t) {
        int m = s.length();
        int n = t.length();
        if(m > n) return false;  // s长度大于t
        // dp数组,dp[i][j]表示以s[i-1]结尾的s和以t[j-1]结尾的t相同子序列的长度
        vector> dp(m+1, vector(n+1));  
        for(int i = 1; i <= m; i++){
            for(int j = 1; j <= n; j++){
                if(s[i - 1] == t[j - 1]) dp[i][j] = dp[i-1][j-1] + 1;
                else dp[i][j] = dp[i][j-1];
            }
        }
        return dp[m][n] == m ? true : false;
    }
};

从状态转移公式可以看到,如果s是t的子序列,即便s最后一个字符并不是和t的最后一个字符匹配,dp[m][n]也会从dp[m][j < n]的状态传递得到dp[m][n]==m,因此只需要判断dp[m][n]是否等于s的长度即可。

LeetCode 115 不同的子序列

题目链接:115. 不同的子序列 - 力扣(Leetcode)

 这一题相比上一题要难很多,虽然知道dp数组应该怎么定义,但怎么进行状态推导有点难想。dp[i][j]定义为以s[i-1]为结尾的s包含的以t[j-1]为结尾的子序列的个数,同样是分s[i-1]==t[j-1]和s[i-1] != t[j-1]两种情况。s[i-1]==t[j-1]时,考虑是否使用s[i-1]做匹配又可以分为两种情况,像示例中的rabbbit和rabbit的匹配中,可以从s中三个连续连续的b选两个进行匹配。因此,如果选择s[i-1]做匹配,需要的是dp[i-1][j-1],而不考虑当前的s[i-1]时需要的是dp[i-1][j](也就是当前的t[j-1]可能和s[i-1]之前的字符匹配),题目求的是匹配的种数,所以是两种情况的种数相加。而不相等时,t[j-1]只能和s[i-1]之前的字符匹配,dp[i][j]只有dp[i-1][j]一个来源。以下是代码随想录中的C++实现。

class Solution {
public:
    int numDistinct(string s, string t) {
        vector> dp(s.size() + 1, vector(t.size() + 1));
        for (int i = 0; i < s.size(); i++) dp[i][0] = 1;
        for (int j = 1; j < t.size(); j++) dp[0][j] = 0;
        for (int i = 1; i <= s.size(); i++) {
            for (int j = 1; j <= t.size(); j++) {
                if (s[i - 1] == t[j - 1]) {
                    dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
                } else {
                    dp[i][j] = dp[i - 1][j];
                }
            }
        }
        return dp[s.size()][t.size()];
    }
};

如果将dp数组的定义修改一下,改为以s[j-1]为结尾的s包含的以t[i-1]为结尾的子序列的个数,这道题不相等时dp来源的方向其实和判断子序列中一样,只是字符相等时状态转移的内涵更丰富,并且要注意初始化的问题:

class Solution {
public:
    int numDistinct(string s, string t) {
        int m = t.length();
        int n = s.length();
        if(m > n) return false;  // s长度大于t
        // dp数组
        vector> dp(m+1, vector(n+1));
        // 初始化 以j-1为结尾的s可以随便删除元素,出现空字符串t的个数为1
        for(int i=0; i<=n; i++) dp[0][i] = 1;

        for(int i = 1; i <= m; i++){
            for(int j = 1; j <= n; j++){
                if(t[i - 1] == s[j - 1]) {
                    dp[i][j] = dp[i-1][j-1] + dp[i][j-1];
                }
                else {
                    dp[i][j] = dp[i][j-1];
                }
            }
        }
        return dp[m][n];
    }
};

你可能感兴趣的:(刷题,算法,动态规划,leetcode)