LeetCode 392 判断子序列
文章目录
- 摘要
- 描述
- 题解答案
- 题解代码分析
- 代码解析
- 示例测试及结果
- 时间复杂度
- 空间复杂度
- 总结
摘要
在字符串的日常处理场景里,判断一个字符串是不是另一个字符串的子序列是非常常见的需求。比如在搜索推荐里,我们输入一个简写,系统要判断这个简写是不是某个候选词的子序列,从而决定是否推荐。LeetCode 392 就是这样一道题:给定两个字符串 s
和 t
,判断 s
是否为 t
的子序列。
本文会从题目分析、解题思路、Swift 实现代码到示例测试,带大家完整拆解这道题,并结合进阶问题聊聊在大规模数据场景下的优化思路。
描述
题目的要求很直白:
- 子序列的定义是 删除原字符串的部分字符,但保留剩余字符的相对顺序。
- 比如
"abc"
是"ahbgdc"
的子序列,因为我们可以依次找到'a' -> 'b' -> 'c'
,而不用考虑中间的'h'
,'g'
,'d'
。 - 但
"axc"
就不是"ahbgdc"
的子序列,因为虽然有'a'
,但找不到'x'
。
换句话说,这就是一个 顺序匹配问题。
题解答案
解决思路其实非常直接:
- 用两个指针,一个扫描
s
,一个扫描t
。 - 如果两个指针指向的字符相同,就让
s
的指针前进;不管相不相同,t
的指针总是往前。 - 如果
s
的指针走到了末尾,就说明s
是t
的子序列。
这种方法特别高效,时间复杂度线性,非常适合这类顺序匹配问题。
题解代码分析
下面是 Swift 的实现代码:
import Foundationclass SubsequenceChecker {func isSubsequence(_ s: String, _ t: String) -> Bool {// 转成数组方便按下标访问let sChars = Array(s)let tChars = Array(t)var i = 0, j = 0while i < sChars.count && j < tChars.count {if sChars[i] == tChars[j] {i += 1 // 匹配成功,移动 s 指针}j += 1 // 不管怎样,t 指针都要前进}return i == sChars.count}
}
代码解析
-
字符串转数组:
在 Swift 中字符串不是直接按下标可访问的,所以需要转成字符数组,方便用下标访问。 -
双指针:
i
指向s
,j
指向t
。- 如果当前字符相同,就把
i
向前推进。 j
每次都要前进,保证扫描完整个t
。
-
终止条件:
- 如果
i
成功走到s
的末尾,说明所有字符都匹配了。 - 如果
t
扫描完了,但i
还没走到末尾,那就说明匹配失败。
- 如果
示例测试及结果
let checker = SubsequenceChecker()print(checker.isSubsequence("abc", "ahbgdc"))
// 输出: true
// 因为可以在 "ahbgdc" 里依次找到 a -> b -> cprint(checker.isSubsequence("axc", "ahbgdc"))
// 输出: false
// 因为 "ahbgdc" 里没有 xprint(checker.isSubsequence("", "ahbgdc"))
// 输出: true
// 空串永远是任何字符串的子序列
时间复杂度
在最坏情况下,我们需要扫描完整个 t
。
- 时间复杂度是 O(n),其中
n
是字符串t
的长度。
空间复杂度
除了把字符串转成数组,我们没有额外使用额外的存储。
- 空间复杂度是 O(1)(如果忽略输入转数组的开销)。
总结
这道题的核心思路是 双指针顺序匹配,思路清晰,代码也简洁。它不仅在刷题里有用,实际场景中也很常见:
- 比如在模糊搜索时,判断关键字是否是候选词的子序列;
- 在命令行工具里,检查简写命令是否可以映射到完整命令;
- 在推荐系统里,输入简短的前缀时,判断是否能匹配到目标词。
如果进一步考虑 进阶问题(大量 s
要判断是否为 t
的子序列),那就需要对 t
做一次预处理,比如构建一个 字符到下标的映射表,用二分搜索加速匹配,这样查询的效率会更高。