LeetCode 392 判断子序列

1. 题目

392. 判断子序列

题目描述

给定字符串 st ,判断 s 是否为 t 的子序列。

字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace""abcde"的一个子序列,而"aec"不是)。

示例

输入:s = "abc", t = "ahbgdc"

输出:true

输入:s = "axc", t = "ahbgdc"

输出:false

约束

  • (0 <= s.length <= 100)

  • (0 <= t.length <= 10^4)

  • 两个字符串都只由小写英文字母组成

进阶:如果有大量输入的 S,称作 S1, S2, ... , Sk 其中 k >= 10 亿,你需要依次检查它们是否为 T 的子序列。在这种情况下,你会怎样改变代码?

2. 最佳解题思路描述(双指针贪心,单次遍历最优)

  1. 定义指针 p 指向 s 当前待匹配字符,初始为 0;

  2. 遍历长字符串 t每一个字符:

    • 若当前 t[i] == s[p],说明匹配成功,p 向后移动一位;

    • p 已经走完 s 全部字符,可提前跳出循环;

  3. 遍历结束后,若 p == s.size() 说明 s 全部匹配完成,返回 true;否则 false。

优势

  • 时间复杂度 (O(len(t))),仅遍历一次 t;

  • 空间复杂度 (O(1)),仅一个指针变量;

  • 逻辑极简,贪心思想:匹配到字符立刻消耗 s 的一位,保证相对顺序。

3. 我的代码(逻辑完全正确,存在冗余变量、细节优化点)

复制代码
class Solution {
public:
    bool isSubsequence(string s, string t) {
        bool flag = false;
        int len = s.size();
        int n = 0;
        for(int i = 0;i<t.size();i++){
            if(s[n] == t[i]){
                n++;
            }
        }
        if(n == len){
            return true;
        }
        return false;
    }
};

代码说明

  1. 正确性:无逻辑 bug,所有测试用例均可通过,是基础标准写法;

  2. 冗余 / 可优化点:

    • 定义了flag变量但全程未使用,无意义,直接删除;

    • 缺少提前终止:当n == len时 s 已全部匹配,无需继续遍历 t 剩余字符;

    • 变量命名语义差:n改为p/ptr更直观,len可直接使用s.size()无需单独存储;

    • 边界场景兼容:s 为空字符串时s.size()=0,直接返回 true,代码天然支持。

4. 规范优化版代码(增加提前跳出,精简变量)

复制代码
class Solution {
public:
    bool isSubsequence(string s, string t) {
        int ptr = 0;
        int sLen = s.size();
        for (char c : t) {
            if (ptr == sLen) break; // 匹配完成直接退出
            if (c == s[ptr]) ptr++;
        }
        return ptr == sLen;
    }
};

5. 总结

  1. 你的双指针思路完全正确,能 AC;仅存在无用变量、无提前剪枝的小瑕疵;

  2. 核心判定条件:匹配计数等于 s 总长度;

  3. 空字符串 s 一定是任意 t 的子序列,代码天然兼容;

  4. 基础场景直接用一次遍历双指针,简洁高效。

6. 相关知识拓展

拓展 1:进阶海量 s 查询场景(预处理 t + 二分查找)

当 t 固定、有百万 / 十亿个 s 需要判断时,单次遍历 t 会超时;预处理 t 记录每个字母出现的下标数组,对 s 每个字符二分查找下一个匹配位置:

复制代码
#include <vector>
#include <algorithm>
class Solution {
public:
    bool isSubsequence(string s, string t) {
        vector<vector<int>> pos(26);
        // 预处理每个字母在t中出现的所有下标
        for (int i = 0; i < t.size(); i++) {
            pos[t[i] - 'a'].push_back(i);
        }
        int curIdx = -1;
        for (char c : s) {
            int idx = c - 'a';
            // 二分找大于curIdx的最小下标
            auto& vec = pos[idx];
            auto it = upper_bound(vec.begin(), vec.end(), curIdx);
            if (it == vec.end()) return false;
            curIdx = *it;
        }
        return true;
    }
};

预处理(O(len(t))),单次查询(O(len(s) \log len(t))),适合多次查询。

拓展 2:递归写法(不推荐,存在栈开销)

复制代码
bool isSubsequence(string s, string t) {
    if (s.empty()) return true;
    if (t.empty()) return false;
    if (s[0] == t[0])
        return isSubsequence(s.substr(1), t.substr(1));
    else
        return isSubsequence(s, t.substr(1));
}

拓展 3:复杂度对比

  1. 基础双指针(优化后):单次查询 (O(len(t))),(O(1)) 空间;适合少量查询;

  2. 预处理二分法:预处理(O(len(t))),单次(O(len(s)\log len(t))),(O(26 \times len(t)))空间;适合海量 s 查询。

拓展 4:易错点复盘

  1. 忘记提前终止循环,长 t 会做多余遍历;

  2. 新增无使用的冗余变量,代码可读性下降;

  3. 混淆子序列与子串:子串要求连续,子序列只要求相对顺序不变。

相关推荐
摇滚侠2 小时前
《SpringBoot 3:入门与应用实战》第 7 章 AOP 思想与实现 阅读笔记 15
java·spring boot·笔记
摇滚侠2 小时前
《SpringBoot 3:入门与应用实战》第 7 章 AOP 思想与实现 阅读笔记 14
spring boot·笔记·后端
重生之后端学习2 小时前
239. 滑动窗口最大值[困难]✅
java·数据结构·算法·leetcode·职场和发展
fpcc2 小时前
算法和数据结构—动态规划法
数据结构·算法·动态规划
陈年老古董4 小时前
OpenCV图像处理笔记:图像拼接、答题卡识别与目标提取
人工智能·笔记·python·opencv·计算机视觉
陈年老古董5 小时前
CentOS编译安装Python3.11完整教程
linux·笔记·学习·centos·python3.11
星星.7225 小时前
C++算法竞赛|二分查找与二分答案:边界模板、浮点二分、STL
数据结构·c++·算法
sel_95 小时前
【OPD论文导读(二)】OPD(On-Policy Distillation)全景调研:十篇论文讲透“在自己生成的内容上学习“这件事
人工智能·python·深度学习·学习·算法·语言模型
动词ing5 小时前
【学习笔记】数据结构(数组长度和关键特性+快慢指针+左右指针)
数据结构·笔记·学习