LeetCode 面试经典 150 题之判断子序列解题详解

在 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 等边界情况),进一步验证对思路的理解,确保在面试中能应对各种变形问题。

如果大家有其他更优的解法思路,或在代码实现中遇到问题,欢迎在评论区留言讨论!

相关推荐
代码欢乐豆13 小时前
编译原理机测客观题(7)优化和代码生成练习题
数据结构·算法·编译原理
Scc_hy14 小时前
强化学习_Paper_2000_Eligibility Traces for Off-Policy Policy Evaluation
人工智能·深度学习·算法·强化学习·rl
leke200314 小时前
2025年10月17日
算法
CoovallyAIHub14 小时前
Mamba-3震撼登场!Transformer最强挑战者再进化,已进入ICLR 2026盲审
深度学习·算法·计算机视觉
Aqua Cheng.14 小时前
代码随想录第七天|哈希表part02--454.四数相加II、383. 赎金信、15. 三数之和、18. 四数之和
java·数据结构·算法·散列表
怀揣小梦想14 小时前
跟着Carl学算法--哈希表
数据结构·c++·笔记·算法·哈希算法·散列表
Nebula_g14 小时前
Java哈希表入门详解(Hash)
java·开发语言·学习·算法·哈希算法·初学者
Kent_J_Truman14 小时前
【模拟散列表】
数据结构·算法·蓝桥杯·散列表·常识类
Lchiyu15 小时前
哈希表 | 454.四数相加II 383. 赎金信 15. 三数之和 18. 四数之和
算法
玩镜的码农小师兄15 小时前
[从零开始面试算法] (04/100) LeetCode 136. 只出现一次的数字:哈希表与位运算的巅峰对决
c++·算法·leetcode·面试·位运算·hot100