day55 | 392.判断子序列、115.不同的子序列

目录:

解题及思路学习

392.判断子序列

https://leetcode.cn/problems/is-subsequence/

给定字符串 s 和 t ,判断 s 是否为 t 的子序列。

字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace""abcde"的一个子序列,而"aec"不是)。

示例 1:

输入:s = "abc", t = "ahbgdc"
输出:true

思考:这道题感觉可以遍历t,在遍历的途中,用一个index记录s中的元素。如果s中的元素在t中能找到,就index + 1。但index 等于s.size() -1, 则返回true。如果遍历结束并没有,则返回false。

class Solution {
public:
    bool isSubsequence(string s, string t) {
        if (s.size() == 0) return true;
        int index = 0;
        for (int i = 0; i < t.size(); i++) {
            if (s[index] == t[i]) index++;
            if (index > s.size()-1) return true;
        }
        return false;
    }
};

时间复杂度:O(n)

空间复杂度:O(1)

随想录:如果s和t的最长公共子序列长度等于s,则说明s是t的子序列。

1、含义

dp[i][j] 表示以下标i-1为结尾的字符串s,和以下标j-1为结尾的字符串t,相同子序列的长度为dp[i][j]

2、递推公式

在确定递推公式的时候,首先要考虑如下两种操作,整理如下:

  • if (s[i - 1] == t[j - 1])
    • t中找到了一个字符在s中也出现了
  • if (s[i - 1] != t[j - 1])
    • 相当于t要删除元素,继续匹配

if (s[i - 1] == t[j - 1]),那么dp[i][j] = dp[i - 1][j - 1] + 1;,因为找到了一个相同的字符,相同子序列长度自然要在dp[i-1][j-1]的基础上加1。

if (s[i - 1] != t[j - 1]),此时相当于t要删除元素,t如果把当前元素t[j - 1]删除,那么dp[i][j] 的数值就是 看s[i - 1]与 t[j - 2]的比较结果了,即:dp[i][j] = dp[i][j - 1];

3、dp数组如何初始化

从递推公式可以看出dp[i][j]都是依赖于dp[i - 1][j - 1] 和 dp[i][j - 1],所以dp[0][j]和dp[i][0]是一定要初始化的。

这里大家已经可以发现,在定义dp[i][j]含义的时候为什么要表示以下标i-1为结尾的字符串s,和以下标j-1为结尾的字符串t,相同子序列的长度为dp[i][j]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HUyCtfUd-1689565832540)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/9c774559-8c2e-4242-819d-f9a2e7b259ce/Untitled.png)]

4、遍历顺序

从上往下,从左到右

5、举例推导dp数组

以示例一为例,输入:s = “abc”, t = “ahbgdc”,dp状态转移图如下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ebBN4FRE-1689565832544)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/355d2733-357e-4d79-8416-f9e61f1b11db/Untitled.png)]

dp[i][j]表示以下标i-1为结尾的字符串s和以下标j-1为结尾的字符串t 相同子序列的长度,所以如果dp[s.size()][t.size()] 与 字符串s的长度相同说明:s与t的最长相同子序列就是s,那么s 就是 t 的子序列。

class Solution {
public:
    bool isSubsequence(string s, string t) {
        vector<vector<int>> dp(s.size() + 1, vector<int>(t.size() + 1, 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] + 1;
                else dp[i][j] = dp[i][j - 1];
            }
        }
        if (dp[s.size()][t.size()] == s.size()) return true;
        return false;
    }
};
  • 时间复杂度:O(n × m)
  • 空间复杂度:O(n × m)

这道题目算是编辑距离的入门题目(毕竟这里只是涉及到减法),也是动态规划解决的经典题型。

115.不同的子序列

https://leetcode.cn/problems/distinct-subsequences/

给你两个字符串 s ****和 t ,统计并返回在 s 的 子序列 中 t 出现的个数。

题目数据保证答案符合 32 位带符号整数范围。

示例 1:

输入:s = "rabbbit", t = "rabbit"输出:3

思考:

双指针思路—如果用双指针的话,从前往后遍历t。如果遇到相等,则index往后移,如果t中的数字等于s中index的前一个,则记录次数的count+1。最后如果遍历完了s数组,则返回count,否则返回0.

dp思路—也是公共子序列。用一个数值来统计次数。

随想录:

1、dp含义

dp[i][j]:以i-1为结尾的s子序列中出现以j-1为结尾的t的个数为dp[i][j]。

2、递推公式

这一类问题,基本是要分析两种情况

  • s[i - 1] 与 t[j - 1]相等
  • s[i - 1] 与 t[j - 1] 不相等

当s[i - 1] 与 t[j - 1]相等时,dp[i][j]可以有两部分组成。

一部分是用s[i - 1]来匹配,那么个数为dp[i - 1][j - 1]。即不需要考虑当前s子串和t子串的最后一位字母,所以只需要 dp[i-1][j-1]。

一部分是不用s[i - 1]来匹配,个数为dp[i - 1][j]。

当s[i - 1] 与 t[j - 1]不相等时,dp[i][j]只有一部分组成,不用s[i - 1]来匹配(就是模拟在s中删除这个元素),即:dp[i - 1][j]

所以递推公式为:dp[i][j] = dp[i - 1][j];

3、初始化

从递推公式dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]; 和 dp[i][j] = dp[i - 1][j]; 中可以看出dp[i][j] 是从上方和左上方推导而来。那么 dp[i][0] 和dp[0][j]是一定要初始化的。

dp[i][0] 表示:以i-1为结尾的s可以随便删除元素,出现空字符串的个数。

那么dp[i][0]一定都是1,因为也就是把以i-1为结尾的s,删除所有元素,出现空字符串的个数就是1。

再来看dp[0][j],dp[0][j]:空字符串s可以随便删除元素,出现以j-1为结尾的字符串t的个数。

那么dp[0][j]一定都是0,s如论如何也变成不了t。

4、遍历顺序

从上到下,从左到右,这样保证dp[i][j]可以根据之前计算出来的数值进行计算。

5、举例推导dp数组

以s:“baegg”,t:"bag"为例,推导dp数组状态如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l26ZQiZj-1689565832546)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/6454230e-5c87-48f0-b29a-963c6c00af05/Untitled.png)]

class Solution {
public:
    int numDistinct(string s, string t) {
        vector<vector<uint64_t>> dp(s.size() + 1, vector<uint64_t>(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()];
    }
};
  • 时间复杂度: O(n * m)
  • 空间复杂度: O(n * m)

这道题的初始化方式跟之前不一样,是按照定义来推导的。然后在推到公式的环节,将i分为了匹配和不用其匹配两个状态。

知识点记录

知识点

子序列问题-编辑距离问题

个人反思

多按照动态规划的思路去做。

你可能感兴趣的:(C++,LeetCode,刷题,算法,leetcode,数据结构,动态规划,c++)