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

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

相关推荐
兮山与3 小时前
算法1.0
算法
im_AMBER4 小时前
数据结构 02 线性表
数据结构·算法
2401_841495644 小时前
【计算机视觉】图像去雾技术
人工智能·python·opencv·算法·计算机视觉·技术·图像去雾
河北北重机械9664 小时前
汽车安全性能测试与铸铁底座的重要性
人工智能·算法·机器学习·铸铁底座·铁底座装配·试验台基底座加工
北京地铁1号线4 小时前
数据结构笔试选择题:题组2
算法
XCOSnTh5 小时前
XCOSnTh单片机的串口
c语言·单片机·嵌入式硬件·算法·xcosnth
Yunfeng Peng5 小时前
2- 十大排序算法(希尔排序、计数排序、桶排序)
java·算法·排序算法
bot5556666 小时前
“企业微信iPad协议”凌晨沉默实验:当群发接口只剩心跳声
算法
郝学胜-神的一滴6 小时前
深入理解 C++ 中的 `std::bind`:功能、用法与实践
开发语言·c++·算法·软件工程