在问 · 算法题解

392. 判断子序列

题目描述

输入字符串 s 和 t。判断 s 是不是 t 的子序列。

子序列的意思是:从 t 中删去若干字符(也可以一个都不删),剩余字符不改变先后次序,得到的字符串。例如 "ace" 是 "abcde" 的子序列,"aec" 则不是。

是子序列返回 true,否则返回 false。空串是任何字符串的子序列。

示例 1
输入:s = "abc", t = "ahbgdc"
输出:true

解释:在 t 中能依次找到 a、b、c。

示例 2
输入:s = "axc", t = "ahbgdc"
输出:false

解释:t 中没有字符 x。

数据范围

  • 0 <= s.length <= 100
  • 0 <= t.length <= 10^4
  • 两个字符串都只含小写英文字母

进阶:如果有海量的 s(S1, S2, ..., Sk,k 达到 10 亿级)要逐个判断是否为同一个 t 的子序列,应如何改进?

解题思路

指针 i 指向 s 中下一个待匹配的字符。从左到右扫描 t,遇到与 s[i] 相同的字符就把 i 前移。贪心地尽早匹配不会吃亏:更早匹配只会给后面留下更多字符。扫完后 i 等于 len(s) 就是子序列。

为什么正确

s[0..i) 已经是 t 当前已扫描前缀的子序列,且 i 是所有匹配方式中最大的。

复杂度分析

时间 O(|t|),空间 O(1)。

易错点

空串 s 是任何字符串的子序列;匹配完 s 后要防止 s[i] 越界(先判断 i < len(s))。

参考代码

Python、Java、C++、Go、JavaScript、TypeScript 六种语言,逻辑一致。

class Solution:
    def isSubsequence(self, s: str, t: str) -> bool:
        i = 0
        for c in t:
            if i < len(s) and s[i] == c:
                i += 1
        return i == len(s)

载入编辑器,自己跑一遍 →

其他解法

双指针最简单,适合单次查询;预处理 + 动态规划和二分查找都先处理 t,前者查询更快,后者占用空间更少。

解法 2:预处理 + 动态规划 时间 O(26m + n),空间 O(26m)

设 m 为 t 的长度,n 为 s 的长度。从后往前填表 f[i][c],表示从位置 i 开始字符 c 第一次出现的位置,不存在记为 m。匹配 s 时,当前位置 j 每次直接跳到 f[j][c] + 1。跳到 m 说明匹配失败。

预处理比双指针多占 O(26m) 空间。预处理只做一次,之后每个 s 只需 O(n) 时间,适合进阶问题里大量 s 查询同一个 t 的场景。

class Solution:
    def isSubsequence(self, s, t):
        m = len(t)
        f = [[m] * 26 for _ in range(m + 1)]
        for i in range(m - 1, -1, -1):
            f[i] = f[i + 1][:]
            f[i][ord(t[i]) - 97] = i
        j = 0
        for c in s:
            nxt = f[j][ord(c) - 97]
            if nxt == m:
                return False
            j = nxt + 1
        return True

解法 3:下标列表 + 二分查找 时间 O(m + n log m),空间 O(m)

预处理时记录每个字符在 t 中出现的下标列表。匹配 s 的每个字符时,在它的下标列表里二分查找第一个大于上一次匹配位置的下标。找不到就返回 false。

空间只有 O(m),比动态规划表小。每次匹配多一个 log m 因子。同样适合大量查询的进阶场景。

from bisect import bisect_right


class Solution:
    def isSubsequence(self, s, t):
        pos = [[] for _ in range(26)]
        for i, c in enumerate(t):
            pos[ord(c) - 97].append(i)
        prev = -1
        for c in s:
            lst = pos[ord(c) - 97]
            k = bisect_right(lst, prev)
            if k == len(lst):
                return False
            prev = lst[k]
        return True

双指针 专题的其他题目

查看 双指针 全部题目 →