392. 判断子序列 - 力扣(LeetCode)
给定字符串 s 和 t ,判断 s 是否为 t 的子序列。字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"
是"abcde"
的一个子序列,而"aec"
不是)。
进阶:如果有大量输入的 S,称作 S1, S2, ... , Sk 其中 k >= 10亿,你需要依次检查它们是否为 T 的子序列。在这种情况下,你会怎样改变代码?
示例 1:
输入:s = "abc", t = "ahbgdc" 输出:true
示例 2:
输入:s = "axc", t = "ahbgdc" 输出:false
>>思路和分析
- 只需要计算删除的情况
(一)动规五部曲
1.确定dp数组(dp table)以及下标的含义
- 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
- ② if(s[i-1] != t[j-1]) dp[i][j] = dp[i][j-1]
如果 s[i-1] == t[j-1],说明当前在遍历串t 时找到了一个字符和 串s的字符 匹配,那么相同子序列长度就在dp[i-1][j-1]的基础上加1
如果 s[i-1] != t[j-1],说明当前在遍历串t 时这个字符和 串s的字符 不匹配,此时相当于 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][0] 和 dp[i][0] 是一定要初始化的
vector<vector<int>> dp(s.size() + 1, vector<int>(t.size() + 1, 0));
4.确定遍历顺序
- 从递推式可以看出 dp[i][j] 依赖于 dp[i-1][j-1] 和 dp[i][j-1],遍历顺序应该是从上到下,从左到右
5.举例推导dp数组
- dp[i][j] : 表示以下标 i-1 为结尾的字符串 s ,和以下标 j-1 为结尾的字符串 t ,相同子序列的长度为dp[i][j]
那么dp[s.size()][t.size()] = 3,而s.size() = 3,故 s 是 t 的子序列,返回 true
注:观察此表格我们可以发现,当串s在串t中寻找某一个字符成功时,每一行的最后一个数值都等于当前的 i 值。所以下文代码中有一句 if(dp[i][t.size()] !=i) return false; 说明 串s 中的某一个字符在 串t 中实在无法找不到了,就直接返回false
(1)动态规划 二维dp
class Solution {
public:
// 动态规划 二维dp
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[i][t.size()] !=i) return false;
}
return dp[s.size()][t.size()] == s.size();
}
};
- 时间复杂度:O(n × m)
- 空间复杂度:O(n × m)
(2)二维dp 优化空间
class Solution {
public:
// 二维dp 优化空间复杂度
bool isSubsequence(string s, string t) {
vector<vector<int>> dp(2,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 % 2][j] = dp[(i-1)%2][j-1] + 1;
else dp[i % 2][j] = dp[i % 2][j-1];
}
if(dp[i%2][t.size()] !=i) return false;
}
return dp[s.size() % 2][t.size()] == s.size();
}
};
- 时间复杂度:O(n × m)
- 空间复杂度:O(m)
(3)一维dp 优化空间
class Solution {
public:
// 一维dp 滚动数组 优化空间复杂度
bool isSubsequence(string s, string t) {
vector<int> dp(t.size()+1,0);
for(int i=1;i<=s.size();i++) {
int pre = dp[0];
for(int j=1;j<=t.size();j++) {
int tmp = dp[j];
if(s[i-1] == t[j-1]) dp[j] = pre + 1;
else dp[j] = dp[j-1];
pre = tmp;
}
if(dp[t.size()] !=i) return false;
}
return dp[t.size()] == s.size();
}
};
- 时间复杂度:O(n × m)
- 空间复杂度:O(m)
- 我思考之后发现内层for循环里的 j 的起始位置可以改一下
class Solution {
public:
bool isSubsequence(string s, string t) {
vector<vector<int>> dp(s.size()+1,vector<int>(t.size()+1,0));
int tmp=1;
for(int i=1;i<=s.size();i++) {
bool flag = 0;
for(int j=tmp;j<=t.size();j++) {
if(s[i-1] == t[j-1]) {
dp[i][j] = dp[i-1][j-1] + 1;
if(!flag) tmp=j;
flag=1;
}
else dp[i][j] = dp[i][j-1];
}
if(dp[i][t.size()] !=i) return false;
}
return dp[s.size()][t.size()] == s.size();
}
(二)双指针
(1)while循环写法
class Solution {
public:
bool isSubsequence(string s,string t) {
int i=0,j=0;
while(i<s.size() && j<t.size()) {
if(s[i] == t[j]) {
i++;
j++;
}
else j++;
}
if(i == s.size()) return true;
return false;
}
};
(2)for循环写法
class Solution {
public:
bool isSubsequence(string s,string t) {
if(s.size()==0) return true;
int i=0;
for(auto c:t) {
if(s[i] == c) {
i+=1;
if(i == s.size()) return true;
}
}
return false;
}
};
参考和推荐文章、视频:
代码随想录 (programmercarl.com)https://www.programmercarl.com/0392.%E5%88%A4%E6%96%AD%E5%AD%90%E5%BA%8F%E5%88%97.html#%E6%80%9D%E8%B7%AF动态规划,用相似思路解决复杂问题 | LeetCode:392.判断子序列_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1tv4y1B7ym/?spm_id_from=pageDriver&vd_source=a934d7fc6f47698a29dac90a922ba5a3来自代码随想录课堂视频截图:
- 我写的另一个版本(串 s 用双指针,串 t 也用双指针):
class Solution {
public:
bool isSubsequence(string s,string t) {
int sl=0,sr=s.size()-1;
int tl=0,tr=t.size()-1;
int count = 0;
if(s.size()==0) return true;
while(sl<=sr && tl <= tr) {
if(s[sl] == t[tl]) {
sl++;
tl++;
count++;
}
else tl++;
if(tl < tr && s[sr] == t[tr]) {
sr--;
tr--;
count++;
}
else tr--;
}
if(s.size() == count) return true;
return false;
}
};
把串s看作是一个装着 串s 字符的栈 :
class Solution {
public:
bool isSubsequence(string s,string t) {
if(s.empty()) return true;
for(int i=t.size()-1;i>=0;i--) {
if(t[i] == s.back()) {
s.pop_back();
if(s.empty()) return true;
};
}
return false;
}
};