392. 判断子序列(Is Subsequence)

难度:简单 | 主题:字符串——双指针贪心

题目

给定字符串 st,判断 s 是否为 t 的子序列。子序列不要求连续,但要求相对顺序一致。

示例:

 
s = "abc", t = "ahbgdc" → True('a'、'b'、'c' 按顺序出现)
 
s = "axc", t = "ahbgdc" → False
 

思路

先讲个故事:朋友圈的点赞记录

你翻看一个人的朋友圈,想找他有没有在特定几天发过帖。你手里有一串日期清单(s),朋友圈是时间线(t)。

你不会乱翻——你按时间线从头扫,每看到一个匹配的日期就打个勾。如果所有日期都勾完了,说明这人确实在那些天发过帖。

这就是双指针贪心:s 的每个字符都要在 t 中按顺序找到匹配


引导式推导:从暴力到双指针

第 1 层:暴力 O(n×m)

对 s 的每个字符,在 t 中从头找匹配。

第 2 层:双指针 O(n)

i 指向 s,j 指向 t。如果 s[i] == t[j]i++;无论是否匹配,j++。最后看 i 是否走完了 s。


graph LR

    subgraph 双指针匹配

        A["i 指向 s<br/>j 指向 t"] --> B{"s[i] == t[j]?"}

        B -->|是| C["i++, j++"]

        B -->|否| D["j++ only"]

        C --> E{"i == len(s)?"}

        D --> E

        E -->|是| F["True"]

        E -->|否| A

    end

为什么贪心成立? s 的每个字符只需要在 t 中找到第一个匹配即可。越早匹配,留给后面字符的空间越大。


代码

 
def isSubsequence(self, s, t):
 
    i = 0
 
    j = 0
 
    while i < len(s) and j < len(t):
 
        if s[i] == t[j]:
 
            i += 1
 
        j += 1
 
    return i == len(s)
 

复杂度

指标解释
时间O(n)n 是 t 的长度,每个字符最多访问一次
空间O(1)只用了两个指针

实战考量

频率分析

简单但高频,约 20% 考察双指针基本功。很多场景用到子序列判断(如日志匹配)。

延伸思考

Q:如果有很多个 s 需要判断呢?

A:预处理 t,对每个字符记录它后面每个字符的下一个位置(二分查找优化到 O(m log n))。

Q:如果 t 是数据流(只能读一次)呢?

A:只能在线贪心匹配,无法预处理。

Q:最长公共子序列怎么做?

A:动态规划,二维 DP(1143最长公共子序列)。

易错点

  • j 每次都要前进,i 只在匹配时前进

  • 返回值是 i == len(s) 不是 j == len(t)

  • 空串是任何串的子序列


生活类比

朋友圈点赞记录 → 贪心匹配

你按时间线从头扫,每看到一个匹配的日期就打个勾。

不需要回头找——如果这个日期错过了,后面的更不可能匹配上。

贪心的本质:按顺序,不回头。


相关题目

题目关系
1143最长公共子序列LCS 基础,DP 版
72编辑距离字符串编辑系列
521最长特殊子序列子序列变体

→ 返回题单:LeetCode学习路线图 > 二、字符串

速记卡(面试闪卡)

Q1:一句话讲清「392. 判断子序列(Is Subsequence)」到底是什么?

A:判断字符串 s 是否按原顺序出现在 t 中,双指针贪心一遍扫即可。

Q2:题目理解 —— 什么算子序列? —— 怎么理解?

A:像在朋友圈时间线里找特定几天的点赞:不要求连续,但先后顺序不能乱。s 是清单,t 是时间线。英文:Subsequence。

Q3:核心思路 —— 为什么贪心就够? —— 怎么理解?

A:像按清单打卡,时间线从头扫,每匹配一个就打勾,越早匹配留给后面空间越大,不回头。英文:Two-pointer Greedy。

Q4:代码实现 —— 两指针怎么动? —— 怎么理解?

A:像两人并排走:i 指 s、j 指 t,相等则 i、j 同进,否则只 j 进;最后看 i 是否走完 s。英文:Pointer Advance。

Q5:复杂度与实战 —— 多问几个怎么办? —— 怎么理解?

A:像排班:时间 O(n)、空间 O(1)。若有一堆 s,可预处理 t 记录每个字符下一位置做二分。英文:Preprocessing。

Q6:核心速记主线有哪些?

  • 子序列不连续但保序;双指针 i、j 各走各的

  • 匹配时 i、j 同进,否则只 j 进;返回 i==len(s)

  • 空串是任何串的子序列

  • 多个 s 时预处理 t 的下一位置,二分优化

  • 最长公共子序列则是二维 DP,不贪心

口诀

A:子序列里序为先,

双针贪心不回头;

时间一遍常量占,

勾完清单即过关。

相关链接