目录:
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]),那么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;
}
};
这道题目算是编辑距离的入门题目(毕竟这里只是涉及到减法),也是动态规划解决的经典题型。
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]相等时,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()];
}
};
这道题的初始化方式跟之前不一样,是按照定义来推导的。然后在推到公式的环节,将i分为了匹配和不用其匹配两个状态。
子序列问题-编辑距离问题
多按照动态规划的思路去做。