Number of Matching Subsequences

MediumHashing · Bucketing by Next Character🧩 Hashing & CountingLeetCode #792 ↗Updated

Problem

You get a lowercase string s and a list of lowercase strings words. Count how many entries of words are subsequences of s, i.e. can be obtained by deleting zero or more characters of s without reordering the rest. Duplicate words are counted once per occurrence.
Inputs = "abcde", words = ["a","bb","acd","ace"]Output3
Inputs = "dsahjpjauf", words = ["ahjpjau","ja","ahbwzgqnuk","tnmlanowax"]Output2

1 ≤ s.length ≤ 5·104  ·  1 ≤ words.length ≤ 5000  ·  1 ≤ words[i].length ≤ 50

Try it yourself

Java
class Solution {
    public int numMatchingSubseq(String s, String[] words) {
        
    }
}
Your code is saved in this browser.Submit on LeetCode ↗
Key insight
Instead of re-scanning s once per word, scan s once and let every word wait in a bucket keyed by the next letter it needs. Each character of s advances exactly the words waiting for it.
PatternHashing & Counting

Instinct Need "have I seen this before?", "how many times?" or "group things that look alike"? Trade memory for O(1) lookups with a HashMap, HashSet or int[26].

Memory hack Don't search, remember - every element checks in at the front desk, and the desk answers instantly.

Open the Hashing & Counting guide →

Approaches

Brute ForceTwo-Pointer Check per Word

Time O(W · |s| + L)Space O(1)

For every word walk a pointer through s, advancing the word pointer whenever the characters match. The word is a subsequence iff its pointer reaches the end. Here W = number of words, L = total word length.

Java
public int numMatchingSubseq(String s, String[] words) {
    int count = 0;
    for (String w : words) {
        if (isSubsequence(w, s)) count++;
    }
    return count;
}

private boolean isSubsequence(String w, String s) {
    int j = 0; // next character of w to match
    for (int i = 0; i < s.length() && j < w.length(); i++) {
        if (s.charAt(i) == w.charAt(j)) j++;
    }
    return j == w.length();
}

Simple greedy check, but rescans all of s for every word (~2.5·108 steps worst case).

At a glance

LevelApproachTimeSpace
Brute ForceTwo-Pointer Check per WordO(W · |s| + L)O(1)
BetterIndex Lists + Binary SearchO(|s| + L log |s|)O(|s|)
OptimalWaiting Buckets (Single Scan of s)O(|s| + L)O(W)
Common pitfalls
  • Re-using the same character of s for two letters of a word (e.g. counting "bb" in "abc").
  • Binary search for ≥ prev instead of > prev — same off-by-one bug.
  • Deduplicating words — duplicates must each be counted (a cache is fine, dropping them is not).
Interview tips
  • Start from the two-pointer subsequence check, then ask: why scan s W times?
  • Mention caching results of duplicate words as a cheap practical win.
  • Explain the bucket idea as "every word is waiting for its next letter".
Pattern
Bucket by next needed key → many patterns matched against one stream: process the stream once and advance waiting states.
My notes saved in this browser