AlgoMaster Logo

Number of Matching Subsequences

mediumFrequency8 min readUpdated June 23, 2026

Understanding the Problem

We need to count how many words from the array are subsequences of the string s. A subsequence means we can find the word's characters in s in order, but they don't need to be contiguous. For instance, "acd" is a subsequence of "abcde" because we can pick 'a' at index 0, 'c' at index 2, and 'd' at index 3.

The brute force idea is to check each word independently: for each word, scan through s with two pointers to test whether it's a subsequence. The constraints make this expensive. With up to 5000 words and s up to 50,000 characters long, we might scan through s 5000 times.

The way out is to stop re-reading s for every word. We can either scan s once while advancing all words in parallel (the bucket approach), or precompute where each character appears in s so that checking any word becomes a series of fast lookups (the binary search approach).

Key Constraints:

  • s.length <= 5 * 10^4 → Preprocessing s once is affordable. The goal is to avoid scanning all of s again for every word.
  • words.length <= 5000 → Brute force is O(n m) = 5000 50,000 = 2.5 * 10^8 character comparisons in the worst case, which is borderline within typical limits and motivates a faster method.
  • words[i].length <= 50 → Each word is short, so per-word work of O(L * log m) with binary search stays cheap (L is at most 50).

Approach 1: Brute Force (Check Each Word)

Intuition

Check each word against s using the two-pointer technique. Start a pointer at the beginning of s and a pointer at the beginning of the word. Walk through s, and whenever the current character matches the word's current character, advance the word pointer. If the word pointer reaches the end of the word, the word is a subsequence.

This works because the two-pointer scan is greedy: matching each word character at the earliest possible position in s never blocks a later character that a different choice would have allowed. Any valid match could only have used a position at or after the one we picked.

Algorithm

  1. Initialize a counter to 0
  2. For each word in the words array:
  3. Use two pointers (one on s, one on the word) to check if the word is a subsequence of s
  4. If the word pointer reaches the end of the word, increment the counter
  5. Return the counter

Example Walkthrough

1Start: s = "abcde", words = ["a","bb","acd","ace"], count = 0
0
a
1
b
2
c
3
d
4
e
1/6

Code

The bottleneck is that we scan the entire string s independently for every word. With 5000 words, we walk through s 5000 times. The next approach scans s exactly once and advances every word as it goes.

Approach 2: Character Buckets (Next Letter Pointers)

Intuition

Instead of taking each word and checking it against s, flip the perspective. Scan through s one character at a time, and for each character, advance every word that is currently waiting for that character.

The structure is 26 waiting lists, one per lowercase letter. Each word sits in the list for its next needed character. When we read a character in s, we take everyone from that list, advance their pointer by one, and either count them as done (if they have matched all their characters) or move them to the list for their new next character.

This scans s exactly once, and each character of each word is processed exactly once when its word advances through a list.

Algorithm

  1. Create 26 buckets (one per lowercase letter), each holding a list of (word, index) pairs
  2. For each word, place it in the bucket corresponding to its first character, with index 0
  3. Scan through each character c in s:
    • Take all entries from bucket[c]
    • For each entry, advance its index by 1
    • If the index equals the word's length, increment the match count
    • Otherwise, move the entry to the bucket for its next needed character
  4. Return the match count

Example Walkthrough

1Initial buckets: 'a': [(a,0),(acd,0),(ace,0)], 'b': [(bb,0)], count=0
0
a
1
b
2
c
3
d
4
e
1/7

Code

The bucket approach runs in O(m + T) and processes all words together. The next approach takes a different route: it preprocesses s into an index that answers "where does character X next appear after position P?" with a single binary search, so each word can be checked independently without rescanning s.

Approach 3: Binary Search on Precomputed Positions

Intuition

Before looking at any word, preprocess s by recording where each character appears. For s = "abcde", 'a' appears at position [0], 'b' at [1], 'c' at [2], and so on. For s = "abacd", 'a' appears at positions [0, 2]. Because we record positions in increasing order, each character's list is already sorted.

To check if a word is a subsequence, we no longer scan s. For the first character of the word, take the earliest position where it occurs. For the second character, find the first position that comes after the one we used. For the third character, find the first position after that, and so on. Each "find the next position after X" query is a binary search on that character's sorted position list. Taking the earliest valid position each time is the same greedy choice that made the two-pointer scan correct, so it never rules out a match that exists.

If at any point we can't find a valid position (there's no occurrence of the needed character after our current position), the word is not a subsequence.

Algorithm

  1. Build a position index: for each character 'a' through 'z', store a sorted list of all positions where it appears in s
  2. For each word in the array:
    • Start with prevPos = -1 (meaning we haven't matched anything yet)
    • For each character in the word:
      • Look up the position list for that character
      • Binary search for the smallest position greater than prevPos
      • If no such position exists, the word is not a subsequence (break)
      • Otherwise, update prevPos to this position
    • If all characters were matched, increment the count
  3. Return the count

Example Walkthrough

1Build position index: 'a':[2,7], 'd':[0], 'f':[9], 'h':[3], 'j':[4,6], 'p':[5], 's':[1], 'u':[8]
0
d
1
s
2
a
3
h
4
j
5
p
6
j
7
a
8
u
9
f
1/8

Code