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):
    • 当 si == tj 时,说明 s 的当前字符已在 t 中找到,此时需要匹配 s 的下一个字符,因此 i 和 j 同时右移(i += 1,j += 1)。
    • 当 si != tj 时,说明 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(指向 s0 = 'a'),j = 0(指向 t0 = 'a')。
  • 第一步:s0 == t0,i 变为 1(指向 'b'),j 变为 1(指向 'h')。
  • 第二步:s1 ('b') != t1 ('h'),j 变为 2(指向 'b')。
  • 第三步:s1 == t2,i 变为 2(指向 'c'),j 变为 3(指向 'g')。
  • 第四步:s2 ('c') != t3 ('g'),j 变为 4(指向 'd')。
  • 第五步:s2 ('c') != t4 ('d'),j 变为 5(指向 'c')。
  • 第六步:s2 == t5,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,prejc 表示 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 等边界情况),进一步验证对思路的理解,确保在面试中能应对各种变形问题。

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

相关推荐
菩提树下的凡夫9 分钟前
新版OpenCV5.0在ONNX模型的推理应用
opencv·算法
影寂ldy26 分钟前
C# 三大内置委托(Action / Func / Predicate)+ Lambda
c++·算法·c#
机器学习之心38 分钟前
小龙虾优化算法(COA)驱动的CNN-LSTM多输出回归模型及其SHAP可解释性分析
算法·cnn·lstm·小龙虾优化算法·cnn-lstm多输出回归·shap可解释性分析
阿正的梦工坊1 小时前
【Rust】13-Trait 系统、动态分发与对象安全
算法·安全·rust
言存1 小时前
力扣热题283 移动零
数据结构·算法·leetcode
字节高级特工1 小时前
智能指针原理与使用场景全解析
开发语言·c++·算法
珊瑚里的鱼1 小时前
【动态规划】买卖股票的最佳时机Ⅲ
算法·动态规划
逻辑星辰1 小时前
x-ds-pow-response逆向分析
开发语言·人工智能·python·深度学习·算法
CQU_JIAKE1 小时前
6.9【aAAA]
算法
Lewiis2 小时前
白话桶排序
数据结构·算法·golang·排序算法