在问 · 算法题解

792. 匹配子序列的单词数

中等动态规划多指针分桶力扣原题 ↗

题目描述

输入字符串 s 和字符串数组 words。

若从 s 中删去零个或多个字符、其余字符保持原有顺序后能得到某个字符串,就称它是 s 的子序列。比如 "ace" 是 "abcde" 的子序列。

返回 words 中是 s 的子序列的元素个数。words 中相同的单词各自计数。

示例 1
输入:s = "abcde", words = ["a","bb","acd","ace"]
输出:3

解释:"a"、"acd"、"ace" 是子序列,"bb" 不是。

示例 2
输入:s = "dsahjpjauf"
words = ["ahjpjau","ja","ahbwzgqnuk","tnmlanowax"]
输出:2

解释:只有 "ahjpjau" 和 "ja" 能在 s 中按顺序找到。

数据范围

  • 1 <= s.length <= 5 * 10^4
  • 1 <= words.length <= 5000
  • 1 <= words[i].length <= 50
  • words[i] 和 s 只含小写字母。

解题思路:多指针分桶

对每个单词单独双指针匹配要扫 s 很多遍。换个角度:只扫一遍 s,让所有单词同时匹配。每个单词都在“等”自己的下一个字符,把它挂到这个字符的桶里。读到 s 的字符 c 时,取出 c 桶里的全部单词,各自前进一位:走到末尾就计数,否则挂到新的“下一个字符”桶里。

为什么正确

读完 s 的前缀后,每个未完成的单词恰好在一个桶里,桶名是它下一个需要匹配的字符,且已匹配部分是贪心能匹配的最长前缀。

复杂度分析

时间 O(|s| + 所有单词总长),空间 O(单词数)。

易错点

处理 c 桶时必须先把桶“摘下来”换成空桶,再遍历旧桶。否则如果单词下一个字符还是 c(比如 "bb"),它会被放回正在遍历的桶里,在同一个 s 字符上被重复匹配。

参考代码

Python、Java、C++、Go、JavaScript、TypeScript 六种语言,逻辑一致。

class Solution:
    def numMatchingSubseq(self, s: str, words: List[str]) -> int:
        waiting = defaultdict(list)
        for w in words:
            waiting[w[0]].append((w, 0))
        ans = 0
        for c in s:
            bucket = waiting[c]
            waiting[c] = []
            for w, i in bucket:
                i += 1
                if i == len(w):
                    ans += 1
                else:
                    waiting[w[i]].append((w, i))
        return ans

载入编辑器,自己跑一遍 →

其他解法

分桶法只扫一遍 s,所有单词一起推进,需要提前拿到全部单词;二分法预处理 s 后逐个单词查询,适合单词在线到来的情形。

解法 2:位置表 + 二分查找 时间 O(|s| + Σ|w|·log|s|),空间 O(|s|)

预处理 s,把每个字母出现的下标按升序存成列表。判断一个单词时,维护上一个匹配位置 p(初始为 -1),对每个字母在它的下标列表中二分查找第一个大于 p 的下标,找不到就不是子序列,找到就更新 p。

每个单词独立判断,可以在线处理不断到来的查询,是“判断子序列”进阶问题的标准解法。比分桶法多一个 log|s| 因子。

from bisect import bisect_right


class Solution:
    def numMatchingSubseq(self, s: str, words: List[str]) -> int:
        pos = [[] for _ in range(26)]
        for i, c in enumerate(s):
            pos[ord(c) - 97].append(i)
        ans = 0
        for w in words:
            p = -1
            ok = True
            for c in w:
                lst = pos[ord(c) - 97]
                j = bisect_right(lst, p)
                if j == len(lst):
                    ok = False
                    break
                p = lst[j]
            if ok:
                ans += 1
        return ans

动态规划 专题的其他题目

查看 动态规划 全部题目 →