在 LeetCode 面试经典 150 题中,"判断子序列" 是一道典型的字符串处理题目,它不仅考察对字符串遍历的基础理解,还能延伸出多种优化思路,是面试中考察基础算法能力的常见题型。本文将围绕最经典的双指针解法展开,详细拆解解题逻辑、代码实现及复杂度分析,帮助大家轻松掌握这道题的核心思路。
一、题目概览与链接
首先,我们明确题目要求:给定两个字符串 s 和 t,判断 s 是否为 t 的子序列。子序列的定义是:字符串 s 可以通过删除 t 中的某些字符(不改变剩余字符的相对顺序)得到。例如,若 s = "abc"、t = "ahbgdc",则 s 是 t 的子序列;若 s = "axc"、t = "ahbgdc",则 s 不是 t 的子序列。
想要直接查看题目详情、进行在线练习或提交代码,可以访问 LeetCode 官方链接:LeetCode392.判断子序列
二、核心解法:双指针法深度解析
1. 解法思路推导
判断子序列的核心需求是 "在 t 中按顺序找到 s 的所有字符"。如果我们直接暴力遍历 s 的每个字符,再在 t 中逐个查找,可能会出现重复遍历 t 的情况,效率较低。而双指针法通过 "同步遍历两个字符串" 的方式,能一次性完成判断,避免冗余操作。
具体逻辑如下:
- 定义两个指针 i 和 j,分别指向 s 和 t 的起始位置(初始值均为 0),i 用于追踪 s 中待匹配的字符,j 用于遍历 t 寻找匹配字符。
- 遍历 t(即移动 j):
-
- 当 s[i] == t[j] 时,说明 s 的当前字符已在 t 中找到,此时需要匹配 s 的下一个字符,因此 i 和 j 同时右移(i += 1,j += 1)。
-
- 当 s[i] != t[j] 时,说明 t 的当前字符对匹配 s 无用,只需移动 j 继续遍历 t(j += 1)。
- 循环终止条件:要么 j 遍历完 t(j == len(t)),要么 i 遍历完 s(i == len(s))。若 i 遍历完 s,说明 s 的所有字符都按顺序在 t 中找到,返回 True;否则返回 False。
2. 示例验证
以 s = "abc"、t = "ahbgdc" 为例,我们通过步骤拆解理解双指针的移动过程:
- 初始状态:i = 0(指向 s[0] = 'a'),j = 0(指向 t[0] = 'a')。
- 第一步:s[0] == t[0],i 变为 1(指向 'b'),j 变为 1(指向 'h')。
- 第二步:s[1] ('b') != t[1] ('h'),j 变为 2(指向 'b')。
- 第三步:s[1] == t[2],i 变为 2(指向 'c'),j 变为 3(指向 'g')。
- 第四步:s[2] ('c') != t[3] ('g'),j 变为 4(指向 'd')。
- 第五步:s[2] ('c') != t[4] ('d'),j 变为 5(指向 'c')。
- 第六步:s[2] == t[5],i 变为 3(等于 len(s) = 3),循环终止。此时 i 遍历完 s,返回 True,符合预期结果。
三、代码实现
1. 代码示例
1,Python
python
class Solution:
def isSubsequence(self, s: str, t: str) -> bool:
i = j = 0
while i < len(s) and j < len(t):
if s[i] == t[j]:
i += 1
j += 1
return i == len(s)
2,Java
java
class Solution {
public boolean isSubsequence(String s, String t) {
int m = s.length(), n = t.length();
int i = 0, j = 0;
while (i < m && j < n) {
if (s.charAt(i) == t.charAt(j)) {
++i;
}
++j;
}
return i == m;
}
}
四、复杂度分析
1. 时间复杂度:O (m + n)
其中 m 是字符串 s 的长度,n 是字符串 t 的长度。双指针 i 最多移动 m 次(遍历完 s),j 最多移动 n 次(遍历完 t),两者的总移动次数为 m + n,因此时间复杂度为线性级别,效率极高,即使面对较长的字符串也能快速处理。
2. 空间复杂度:O (1)
整个过程仅使用了两个指针变量 i 和 j,没有使用额外的数组、哈希表等数据结构,所需空间与字符串长度无关,属于常数级空间复杂度,符合最优空间效率要求。
五、拓展思路:预处理优化(针对多组 s 查询场景)
上述双指针法适用于 "单个 s 匹配单个 t" 的场景,但如果存在多组 s 都需要匹配同一个 t(如面试中可能遇到的拓展问题),双指针法会重复遍历 t,效率较低。此时可以通过 "预处理 t" 的方式优化:
- 预处理逻辑:为 t 中的每个位置和每个字符,存储该位置之后该字符第一次出现的索引。例如,用一个二维数组 pre,pre[j][c] 表示 t 中索引 j 之后,字符 c(c 为小写字母)第一次出现的位置。
- 查询时:对于 s 的每个字符,通过 pre 数组快速定位 t 中的匹配位置,无需遍历 t,单次查询时间复杂度降至 O (m),预处理时间复杂度为 O (n * 26)(26 为小写字母数量),空间复杂度为 O (n * 26)。
该思路虽在单组查询场景下不如双指针法简洁,但在多组查询场景下能显著提升效率,体现了 "预处理" 思想在算法优化中的应用,也是面试中可能考察的延伸知识点。
六、总结
"判断子序列" 是一道基础但极具代表性的字符串题目,双指针法是解决该题的最优思路之一,其核心在于 "通过同步遍历两个字符串,减少冗余操作",实现了 O (m + n) 的时间复杂度和 O (1) 的空间复杂度,既高效又简洁。
掌握该方法后,不仅能轻松解决本题,还能将 "双指针" 思想迁移到其他类似问题(如 "合并两个有序数组""最长公共前缀" 等)。建议大家在 LeetCode 上提交代码后,再尝试修改测试用例(如 s 为空、t 长度小于 s 等边界情况),进一步验证对思路的理解,确保在面试中能应对各种变形问题。
如果大家有其他更优的解法思路,或在代码实现中遇到问题,欢迎在评论区留言讨论!