打卡第55天。
今日任务
- 392.判断子序列
- 115.不同的子序列
392.判断子序列
给定字符串 s 和 t ,判断 s 是否为 t 的子序列。
字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"
是"abcde"
的一个子序列,而"aec"
不是)。
进阶:
如果有大量输入的 S,称作 S1, S2, … , Sk 其中 k >= 10亿,你需要依次检查它们是否为 T 的子序列。在这种情况下,你会怎样改变代码?
致谢:
特别感谢 @pbrother 添加此问题并且创建所有测试用例。
示例 1:
输入:s = "abc", t = "ahbgdc"
输出:true
示例 2:
输入:s = "axc", t = "ahbgdc"
输出:false
提示:
0 <= s.length <= 100
0 <= t.length <= 10^4
- 两个字符串都只由小写字符组成。
我的题解
暴力解决
class Solution {
public:
bool isSubsequence(string s, string t) {
int k = -1; int cnt = 0;
for(int i = 0; i < s.size(); i++) {
for(int j = k + 1; j < t.size(); j++) {
if(s[i] == t[j]) {
k = j;
cnt++;
break;
} else {
k = -1;
}
}
if(k == -1 || s[i] != t[k]) return false;
}
if(k + 1 == t.size() && cnt != s.size()) return false;
return true;
}
};
双指针做法
class Solution {
public:
bool isSubsequence(string s, string t) {
int i = 0;
for(int j = 0; j < t.size(); j++) {
if(s[i] == t[j]) i++;
}
return i == s.size();
}
};
代码随想录
-
dp 以及下标的定义
dp[i][j] 表示以下标i-1为结尾的字符串s,和以下标j-1为结尾的字符串t,相同子序列的长度为dp[i][j]。 -
递推公式
在确定递推公式的时候,首先要考虑如下两种操作,整理如下:- i f ( s [ i − 1 ] = = t [ j − 1 ] ) if (s[i - 1] == t[j - 1]) if(s[i−1]==t[j−1])t中找到了一个字符在s中也出现了
- i f ( s [ i − 1 ] ! = t [ j − 1 ] ) if (s[i - 1] != t[j - 1]) if(s[i−1]!=t[j−1])相当于t要删除元素,继续匹配
i f ( s [ i − 1 ] = = t [ j − 1 ] ) if (s[i - 1] == t[j - 1]) if(s[i−1]==t[j−1]),那么 d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] + 1 ; dp[i][j] = dp[i - 1][j - 1] + 1; dp[i][j]=dp[i−1][j−1]+1;,因为找到了一个相同的字符,相同子序列长度自然要在dp[i-1][j-1]的基础上加1(如果不理解,在回看一下dp[i][j]的定义)
i f ( s [ i − 1 ] ! = t [ j − 1 ] ) if (s[i - 1] != t[j - 1]) if(s[i−1]!=t[j−1]),此时相当于t要删除元素,t如果把当前元素t[j - 1]删除,那么dp[i][j] 的数值就是 看s[i - 1]与 t[j - 2]的比较结果了,即: d p [ i ] [ j ] = d p [ i ] [ j − 1 ] ; dp[i][j] = dp[i][j - 1]; dp[i][j]=dp[i][j−1];
其实这里 大家可以发现和 1143.最长公共子序列的递推公式基本那就是一样的,区别就是 本题 如果删元素一定是字符串t,而 1143.最长公共子序列 是两个字符串都可以删元素。
class Solution {
public:
bool isSubsequence(string s, string t) {
int n = s.size(), m = t.size();
vector<vector<int> > dp(n + 1, vector<int>(m + 1, 0)); //dp[i][j] 表示以下标i-1为结尾的字符串s,和以下标j-1为结尾的字符串t,相同子序列的长度为dp[i][j];
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
if(s[i - 1] == t[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;
// else dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
else dp[i][j] = dp[i][j - 1];
}
}
return dp[n][m] == n;
}
};
115.不同的子序列
给你两个字符串 s
和 t
,统计并返回在 s
的 子序列 中 t
出现的个数。
题目数据保证答案符合 32 位带符号整数范围。
示例 1:
输入:s = "rabbbit", t = "rabbit"
输出:3
解释:
如下所示, 有 3 种可以从 s 中得到 "rabbit" 的方案。
rabbbit
rabbbit
rabbbit
示例 2:
输入:s = "babgbag", t = "bag"
输出:5
解释:
如下所示, 有 5 种可以从 s 中得到 "bag" 的方案。
babgbag
babgbag
babgbag
babgbag
babgbag
提示:
1 <= s.length, t.length <= 1000
s
和t
由英文字母组成
代码随想录
-
dp以及下标定义
dp[i][j]:以i-1为结尾的s子序列中出现以j-1为结尾的t的个数为dp[i][j]。 -
递推公式
这一类问题,基本是要分析两种情况- 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 - 1] + dp[i - 1][j]; 和 dp[i][j] = dp[i - 1][j]; 中可以看出dp[i][j] 是从上方和左上方推导而来。- dp[i][0] 表示:以 i-1 为结尾的s可以随便删除元素,出现空字符串的个数。一定都是1,因为也就是把以i-1为结尾的s,删除所有元素,出现空字符串的个数就是1。
- dp[0][j]:空字符串s可以随便删除元素,出现以j-1为结尾的字符串t的个数。dp[0][j]:空字符串s可以随便删除元素,出现以j-1为结尾的字符串t的个数。
- 特殊:dp[0][0]应该是1,空字符串s,可以删除0个元素,变成空字符串t。
-
遍历顺序
从递推公式中可以看出dp[i][j]都是根据左上方和正上方推出来的。所以遍历的时候一定是从上到下,从左到右 -
举例dp数组
class Solution {
public:
int numDistinct(string s, string t) {
int n = s.size(), m = t.size();
vector<vector<uint64_t> > dp(n + 1, vector<uint64_t>(m + 1, 0)); // p[i][j]:以i-1为结尾的s子序列中出现以j-1为结尾的t的个数为dp[i][j]。
for (int i = 0; i <= n; i++) dp[i][0] = 1;
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; 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[n][m];
}
};