← 返回题目列表

如何判断一个字符串是否是另一个字符串的子序列?(LeetCode 392)

高频 简单 第 2 / 25 题 更新于 2026/07/30
字符串算法子序列双指针预处理

简化版

判断 s 是否是 t 的子序列,用双指针:i 指向 sj 指向 t。扫描 t,如果 s[i] == t[j],说明匹配到一个字符,i++;无论是否匹配,j 都继续右移。最后如果 i == s.length(),说明 s 全部按顺序匹配成功。

详细版

boolean isSubsequence(String s, String t) {
    int i = 0, j = 0;
    while (i < s.length() && j < t.length()) {
        if (s.charAt(i) == t.charAt(j)) {
            i++;
        }
        j++;
    }
    return i == s.length();
}
  • 子序列不要求连续,只要求相对顺序不变。
  • 指针 i 表示 s 已经匹配到的位置。
  • 扫描 t 时可以跳过无关字符。
  • 单次判断时间 O(|t|),空间 O(1)。

完整版教学

一、子序列和子串的区别

子串必须连续,子序列可以删除若干字符但不能改变顺序。"ace""abcde" 的子序列,因为可以删除 b,d;但它不是子串,因为不连续。

这一区别决定了解法:子串匹配常用 KMP、滑动窗口、哈希;子序列判断只需要按顺序找字符,用双指针扫描即可。

二、双指针的含义

i 指向待匹配字符串 s 的下一个目标字符,j 指向母串 t 当前扫描位置。如果 s[i] == t[j],就把 i 向右移动,表示这个目标字符已经被匹配;j 每轮都向右移动,因为 t 中每个字符最多使用一次。

s = ace
t = abcde
j 扫到 a -> i 从 0 到 1
j 扫到 c -> i 从 1 到 2
j 扫到 e -> i 从 2 到 3,匹配完成

记忆钩子:子序列判断是在 t 里按顺序“捡字符”,捡齐 s 就成功。

三、为什么贪心匹配最早出现的位置是正确的

t[j] 可以匹配 s[i] 时,选择最早的这个位置不会让后续更差。因为越早匹配当前字符,留给后续字符的 t 后缀越长,可选择空间越大。

如果跳过这个可匹配位置,去用后面另一个相同字符,后续可用范围只会变短,不会变长。因此“遇到就匹配”的贪心策略是安全的。

四、手推失败例子

s="aec"t="abcde"

匹配 a 成功,i 指向 e
t 继续扫 b、c、d,都不是 e
扫到 e,匹配成功,i 指向 c
t 已经结束,c 没有机会再匹配

虽然 t 中有 c,但它出现在 e 之前,顺序不符合,所以失败。这说明子序列不仅看字符是否存在,还看相对顺序。

五、多次查询时如何优化

如果只判断一次,双指针 O(|t|) 足够。如果有大量 s 要查询同一个 t,可以预处理 t 中每个字符出现的位置列表,然后对每个 s 用二分查找下一个大于当前位置的出现位置。

场景做法复杂度
单次查询双指针扫描 tO(
多个 s 查询同一个 t预处理位置列表 + 二分预处理 O(
小写字母且追求极致next 数组自动机预处理 O(26

面试若题目追加“有十亿个 s 要判断”,就要主动说预处理。

六、边界与复杂度

空字符串是任何字符串的子序列,所以 s="" 应返回 true。若 s.length() > t.length(),可以提前返回 false,因为母串字符数量不够。

双指针版本中,j 最多走完整个 ti 最多走完整个 s,时间 O(|t|),空间 O(1)。严格说循环次数不超过 t.length()

七、常见误区与追问

  • 误区:要求字符连续。 那是子串,不是子序列。
  • 误区:只比较字符频次。 频次相同不代表顺序正确,例如 "aec" 不是 "abcde" 的子序列。
  • 误区:匹配失败后回退 j。 子序列允许跳过 t 中字符,但不允许回头。
  • 追问:空字符串是不是子序列? 是,删除母串所有字符即可得到空串。
  • 追问:大量查询怎么优化? 预处理 t 的字符位置列表,用二分找每个字符的下一个位置。
  • 追问:和编辑距离有什么区别? 编辑距离允许插删改并求最小代价;子序列判断只问能否删除母串若干字符得到 s。

八、加强记忆

判断子序列 = 在 t 中按顺序捡齐 si 等待下一个要匹配的字符,j 扫描母串;遇到相等就 i++j 永远向右。单次查询 O(|t|),多次查询同一母串时再考虑位置列表或 next 自动机。