在这里插入图片描述
在这里插入图片描述

摘要

在字符串的日常处理场景里,判断一个字符串是不是另一个字符串的子序列是非常常见的需求。比如在搜索推荐里,我们输入一个简写,系统要判断这个简写是不是某个候选词的子序列,从而决定是否推荐。LeetCode 392 就是这样一道题:给定两个字符串 st,判断 s 是否为 t 的子序列。

本文会从题目分析、解题思路、Swift 实现代码到示例测试,带大家完整拆解这道题,并结合进阶问题聊聊在大规模数据场景下的优化思路。

描述

题目的要求很直白:

  • 子序列的定义是 删除原字符串的部分字符,但保留剩余字符的相对顺序
  • 比如 "abc""ahbgdc" 的子序列,因为我们可以依次找到 'a' -> 'b' -> 'c',而不用考虑中间的 'h', 'g', 'd'
  • "axc" 就不是 "ahbgdc" 的子序列,因为虽然有 'a',但找不到 'x'

换句话说,这就是一个 顺序匹配问题

题解答案

解决思路其实非常直接:

  1. 用两个指针,一个扫描 s,一个扫描 t
  2. 如果两个指针指向的字符相同,就让 s 的指针前进;不管相不相同,t 的指针总是往前。
  3. 如果 s 的指针走到了末尾,就说明 st 的子序列。

这种方法特别高效,时间复杂度线性,非常适合这类顺序匹配问题。

题解代码分析

下面是 Swift 的实现代码:

import Foundation

class SubsequenceChecker {
    func isSubsequence(_ s: String, _ t: String) -> Bool {
        // 转成数组方便按下标访问
        let sChars = Array(s)
        let tChars = Array(t)
        
        var i = 0, j = 0
        while i < sChars.count && j < tChars.count {
            if sChars[i] == tChars[j] {
                i += 1 // 匹配成功,移动 s 指针
            }
            j += 1 // 不管怎样,t 指针都要前进
        }
        return i == sChars.count
    }
}

代码解析

  1. 字符串转数组
    在 Swift 中字符串不是直接按下标可访问的,所以需要转成字符数组,方便用下标访问。

  2. 双指针

    • i 指向 sj 指向 t
    • 如果当前字符相同,就把 i 向前推进。
    • j 每次都要前进,保证扫描完整个 t
  3. 终止条件

    • 如果 i 成功走到 s 的末尾,说明所有字符都匹配了。
    • 如果 t 扫描完了,但 i 还没走到末尾,那就说明匹配失败。

示例测试及结果

let checker = SubsequenceChecker()

print(checker.isSubsequence("abc", "ahbgdc"))
// 输出: true
// 因为可以在 "ahbgdc" 里依次找到 a -> b -> c

print(checker.isSubsequence("axc", "ahbgdc"))
// 输出: false
// 因为 "ahbgdc" 里没有 x

print(checker.isSubsequence("", "ahbgdc"))
// 输出: true
// 空串永远是任何字符串的子序列

时间复杂度

在最坏情况下,我们需要扫描完整个 t

  • 时间复杂度是 O(n),其中 n 是字符串 t 的长度。

空间复杂度

除了把字符串转成数组,我们没有额外使用额外的存储。

  • 空间复杂度是 O(1)(如果忽略输入转数组的开销)。

总结

这道题的核心思路是 双指针顺序匹配,思路清晰,代码也简洁。它不仅在刷题里有用,实际场景中也很常见:

  • 比如在模糊搜索时,判断关键字是否是候选词的子序列;
  • 在命令行工具里,检查简写命令是否可以映射到完整命令;
  • 在推荐系统里,输入简短的前缀时,判断是否能匹配到目标词。

如果进一步考虑 进阶问题(大量 s 要判断是否为 t 的子序列),那就需要对 t 做一次预处理,比如构建一个 字符到下标的映射表,用二分搜索加速匹配,这样查询的效率会更高。

Logo

加入「COC·上海城市开发者社区」,成就更好的自己!

更多推荐