题目
简单
相关标签
给定字符串 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
- 两个字符串都只由小写字符组成。
思路和解题方法 1 双指针
- 采用了双指针的思想,用两个指针 i 和 j 分别指向字符串 s 和 t 的开头,并进行比较。
- 如果 s[i] 和 t[j] 相等,则说明 s 中的一个字符已经在 t 中找到了匹配的字符,将 i 加 1。
- 否则,只将 j 加 1,因为当前的 t[j] 不能匹配 s 中的任何字符。
- 当指针 i 到达 s 的结尾时,说明 s 中的所有字符都已经在 t 中按顺序出现,此时返回 true。如果指针 i 没有到达 s 的结尾,但是 j 已经到达了 t 的结尾,则说明 t 中不包含完整的 s ,此时返回 false。
复杂度
时间复杂度:
O(N*M)
时间复杂度:
- 外层循环最多遍历 s 字符串的长度,共需执行 s.size() N 次。
- 内层循环最多遍历 t 字符串的长度,共需执行 t.size() M 次。
- 在每个循环中,进行常数时间(O(1))的比较和赋值操作。 所以,总体时间复杂度为 O(s.size() + t.size()) O(N*M)。
空间复杂度
O(1)
空间复杂度:
- 只需要使用常数级别的额外空间,因此空间复杂度为 O(1)。
c++ 代码 1
// 定义函数 isSubsequence,输入参数为字符串 s 和字符串 t
bool isSubsequence(string s, string t) {
// 获取字符串 s 和 t 的长度
int ss = s.length(),tt = t.length();
// 定义两个指针 i 和 j,初始化为 0
int i = 0 , j = 0;
// 当 i < ss 并且 j < tt 时,执行循环
while(i<ss&&j<tt)
{
// 如果 s[i] 等于 t[j],则将 i 加 1
if(s[i] == t[j])
i++;
// 将 j 加 1
j++;
}
// 如果 i 等于 ss,则返回 true,否则返回 false
return i == ss;
}
思路和解题方法 2 动态规划
声明一个二维动态数组 dp,其中 dp[i][j] 表示 s 的前 i 个字符和 t 的前 j 个字符是否可以匹配成一个子序列。
遍历 s 和 t 的所有子序列,计算 dp 数组的值。对于每个位置 (i, j),根据以下两种情况更新 dp[i][j] 的值:
a. 如果 s 的第 i 个字符和 t 的第 j 个字符相等,则 dp[i][j] 的值等于 dp[i-1][j-1] + 1,表示当前字符可以匹配成功,并且在之前的匹配基础上多了一个字符。
b. 如果 s 的第 i 个字符和 t 的第 j 个字符不相等,则 dp[i][j] 的值等于 dp[i][j-1],表示当前字符无法匹配,需要跳过。
遍历结束后,如果 dp[s.size()][t.size()] 的值等于 s 的长度,则说明 s 是 t 的子序列,返回 true,否则返回 false。
复杂度
时间复杂度:
O(N*M)
时间复杂度:
- 外层循环遍历 s 字符串的长度,共需执行 s.size() N 次。
- 内层循环遍历 t 字符串的长度,共需执行 t.size() M 次。
- 在每个循环中,进行常数时间(O(1))的比较和赋值操作。 所以,总体时间复杂度为 O(s.size() * t.size()) O(N*M)。
空间复杂度
O(N*M)
空间复杂度:
- 创建了一个二维数组 dp,大小为 (s.size() + 1) * (t.size() + 1) N*M。
- 需要额外的空间来存储计算结果,因此空间复杂度为 O(s.size() * t.size()) O(N*M)。
c++ 代码 2
class Solution {
public:
bool isSubsequence(string s, string t) {
// 声明一个二维动态数组 dp,其中 dp[i][j] 表示 s 的前 i 个字符和 t 的前 j 个字符是否可以匹配成一个子序列
vector<vector<int>> dp(s.size() + 1, vector<int>(t.size() + 1, 0));
// 遍历 s 和 t 的所有子序列,计算 dp 数组的值
for (int i = 1; i <= s.size(); i++) {
for (int j = 1; j <= t.size(); j++) {
// 如果 s 的第 i 个字符和 t 的第 j 个字符相等,则 dp[i][j] 的值等于 dp[i-1][j-1] + 1,否则 dp[i][j] 等于 dp[i][j-1]
if (s[i - 1] == t[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;
else dp[i][j] = dp[i][j - 1];
}
}
// 如果 dp[s.size()][t.size()] 的值等于 s 的长度,则说明 s 是 t 的子序列,返回 true,否则返回 false。
if (dp[s.size()][t.size()] == s.size()) return true;
return false;
}
};
其他c++算法代码
class Solution {
public:
bool isSubsequence(string s, string t) {
// 在字符串 t 的开头插入一个空格,使得下标从1开始
t.insert(t.begin(), ' ');
int len1 = s.size(); // 字符串 s 的长度
int len2 = t.size(); // 字符串 t 的长度
// 定义二维数组 dp,用于存储字符在 t 中的下一个出现位置
vector<vector<int> > dp(len2 , vector<int>(26, 0));
// 遍历所有小写字母
for (char c = 'a'; c <= 'z'; c++) {
int nextPos = -1; // 表示接下来再不会出现该字符
// 从后往前遍历字符串 t
for (int i = len2 - 1; i >= 0; i--) {
dp[i][c - 'a'] = nextPos;
if (t[i] == c)
nextPos = i;
}
}
int index = 0; // 记录当前在 t 中的位置
for (char c : s) {
index = dp[index][c - 'a']; // 获取下一个字符 c 的位置
if (index == -1)
return false; // 如果下一个字符 c 不存在,则返回 false
}
return true; // 如果所有字符都存在,则返回 true
}
};
class Solution {
public:
// 定义函数 isSubsequence,输入参数为字符串 s 和字符串 t,返回 bool 类型
bool isSubsequence(string s, string t) {
// 使用队列来存储字符串 s 中的字符
queue<char> q;
for(auto& c : s){
q.push(c);
}
// 循环遍历字符串 t 中的每个字符
for(auto& c : t){
// 如果队列不为空,且队头元素等于当前字符,则将队头元素出队
if(!q.empty() && c == q.front()) q.pop();
}
// 如果队列为空,说明字符串 s 是字符串 t 的子序列,返回 true,否则返回 false
return q.empty();
}
};
java
- 使用一个二维数组 dp 来存储状态转移值,其中 dp[i][j] 表示字符串 s 的前 i 个字符和字符串 t 的前 j 个字符之间的最长公共子序列长度。
- 通过遍历字符串 s 和 t 的所有字符,根据字符是否相等来更新状态转移值。
- 最后判断 dp[length1][length2] 是否等于字符串 s 的长度,如果相等,则说明字符串 s 是字符串 t 的子序列。
public class Solution {
public boolean isSubsequence(String s, String t) {
// 获取字符串 s 和 t 的长度
int length1 = s.length();
int length2 = t.length();
// 定义一个二维数组 dp,dp[i][j] 表示以 s 的前 i 个字符和 t 的前 j 个字符为结尾的子序列长度
int[][] dp = new int[length1+1][length2+1];
// 遍历字符串 s 的每个字符
for(int i = 1; i <= length1; i++){
// 遍历字符串 t 的每个字符
for(int j = 1; j <= length2; j++){
// 当 s 的第 i 个字符等于 t 的第 j 个字符时
if(s.charAt(i-1) == t.charAt(j-1)){
// 子序列长度加1,取决于前一个字符子序列长度加1
dp[i][j] = dp[i-1][j-1] + 1;
}else{
// 当 s 的第 i 个字符不等于 t 的第 j 个字符时,取决于前一个字符在 t 中的位置
dp[i][j] = dp[i][j-1];
}
}
}
// 判断 dp[length1][length2] 是否等于 s 的长度,如果等于,则 s 是 t 的子序列,返回 true,否则返回 false
if(dp[length1][length2] == length1){
return true;
}else{
return false;
}
}
}
觉得有用的话可以点点赞,支持一下。
如果愿意的话关注一下。会对你有更多的帮助。
每天都会不定时更新哦 >人< 。