Welcome to Subscribe On Youtube
792. Number of Matching Subsequences
Description
Given a string s and an array of strings words, return the number of words[i] that is a subsequence of s.
A subsequence of a string is a new string generated from the original string with some characters (can be none) deleted without changing the relative order of the remaining characters.
- For example,
"ace"is a subsequence of"abcde".
Example 1:
Input: s = "abcde", words = ["a","bb","acd","ace"] Output: 3 Explanation: There are three strings in words that are a subsequence of s: "a", "acd", "ace".
Example 2:
Input: s = "dsahjpjauf", words = ["ahjpjau","ja","ahbwzgqnuk","tnmlanowax"] Output: 2
Constraints:
1 <= s.length <= 5 * 1041 <= words.length <= 50001 <= words[i].length <= 50sandwords[i]consist of only lowercase English letters.
Solutions
Solution 1: Breadth-First Search + Hash Table
The data size of the string $s$ in the question reaches up to $5 \times 10^4$. If you violently enumerate each string $w$ in $words$ to determine whether it is a subsequence of $s$, it is likely to time out.
We might as well divide all the words in $words$ into buckets according to the first letter, that is: divide all the words into $26$ buckets according to the first letter, and each bucket stores all words starting with that letter.
For example, for words = ["a", "bb", "acd", "ace"], we get the following bucketing results:
a: ["a", "acd", "ace"]
b: ["bb"]
Then we start traversing from the first character of $s$, assuming the current character is 'a', and we take all words from the bucket starting with 'a'. For each word taken out, if the word length is $1$ at this time, it means that the word has been matched, and we add $1$ to the answer; otherwise, we remove the first letter of the word and put it into the bucket starting with the next letter. For example, for the word "acd", after removing the first letter 'a', we put it into 'c' in the barrel at the beginning. After this round, the bucketing result becomes:
c: ["cd", "ce"]
b: ["bb"]
After traversing $s$, we get the answer.
In fact, each bucket can only store the subscript $i$ of the word and the position $j$ that the word currently matches, which can save space.
| Time complexity $O(n + \sum_{i=0}^{m-1} | w_i | )$, space complexity $O(m)$. Where $n$ and $m$ are the lengths of $s$ and $words$ respectively, and $ | w_i | $ is the length of $words[i]$. |
Solution 2
We can also first use the array or hash table $d$ to store the subscript of each character in the string $s$, that is, $d[c]$ is an array composed of the subscripts of all characters $c$ in $s$.
Then we traverse each word $w$ in $words$. We use binary search to determine whether $w$ is a subsequence of $s$. If so, add $1$ to the answer. The judgment logic is as follows:
- Define the pointer $i$ to represent the $i$ character currently pointing to the string $s$, and initialize it to $-1$.
- Traverse each character $c$ in the string $w$, and binary search for the first position $j$ that is greater than $i$ in $d[c]$. If it does not exist, it means $w$ is not $s$. subsequence, jump out of the loop directly; otherwise, update $i$ to $d[c][j]$ and continue traversing the next character.
- If all characters in $w$ are traversed, it means that $w$ is a subsequence of $s$.
| Time complexity $O(\sum_{i=0}^{m-1} | w_i | \times \log n)$, space complexity $O(m)$. Where $n$ and $m$ are the lengths of $s$ and $words$ respectively, and $ | w_i | $ is the length of $words[i]$. |
Solution 3
This implementation uses binary search, followed by hash table. It traverses the relevant values and updates its state as each value is processed. A keyed container records values that must be found or updated efficiently. After all required states have been considered, the maintained result is returned.
-
class Solution { public int numMatchingSubseq(String s, String[] words) { Deque<int[]>[] d = new Deque[26]; Arrays.setAll(d, k -> new ArrayDeque<>()); for (int i = 0; i < words.length; ++i) { d[words[i].charAt(0) - 'a'].offer(new int[] {i, 0}); } int ans = 0; for (char c : s.toCharArray()) { var q = d[c - 'a']; for (int t = q.size(); t > 0; --t) { var p = q.pollFirst(); int i = p[0], j = p[1] + 1; if (j == words[i].length()) { ++ans; } else { d[words[i].charAt(j) - 'a'].offer(new int[] {i, j}); } } } return ans; } } // Solution 2 class Solution { public int numMatchingSubseq(String s, String[] words) { Deque<int[]>[] d = new Deque[26]; Arrays.setAll(d, k -> new ArrayDeque<>()); for (int i = 0; i < words.length; ++i) { d[words[i].charAt(0) - 'a'].offer(new int[] {i, 0}); } int ans = 0; for (char c : s.toCharArray()) { var q = d[c - 'a']; for (int t = q.size(); t > 0; --t) { var p = q.pollFirst(); int i = p[0], j = p[1] + 1; if (j == words[i].length()) { ++ans; } else { d[words[i].charAt(j) - 'a'].offer(new int[] {i, j}); } } } return ans; } } // Solution 3 class Solution { private List<Integer>[] d = new List[26]; public int numMatchingSubseq(String s, String[] words) { Arrays.setAll(d, k -> new ArrayList<>()); for (int i = 0; i < s.length(); ++i) { d[s.charAt(i) - 'a'].add(i); } int ans = 0; for (String w : words) { if (check(w)) { ++ans; } } return ans; } private boolean check(String w) { int i = -1; for (int k = 0; k < w.length(); ++k) { int c = w.charAt(k) - 'a'; int j = search(d[c], i); if (j == d[c].size()) { return false; } i = d[c].get(j); } return true; } private int search(List<Integer> t, int x) { int left = 0, right = t.size(); while (left < right) { int mid = (left + right) >> 1; if (t.get(mid) > x) { right = mid; } else { left = mid + 1; } } return left; } } -
class Solution { public: int numMatchingSubseq(string s, vector<string>& words) { vector<queue<pair<int, int>>> d(26); for (int i = 0; i < words.size(); ++i) d[words[i][0] - 'a'].emplace(i, 0); int ans = 0; for (char& c : s) { auto& q = d[c - 'a']; for (int t = q.size(); t; --t) { auto [i, j] = q.front(); q.pop(); if (++j == words[i].size()) ++ans; else d[words[i][j] - 'a'].emplace(i, j); } } return ans; } }; // Solution 2 class Solution { public: int numMatchingSubseq(string s, vector<string>& words) { vector<queue<pair<int, int>>> d(26); for (int i = 0; i < words.size(); ++i) d[words[i][0] - 'a'].emplace(i, 0); int ans = 0; for (char& c : s) { auto& q = d[c - 'a']; for (int t = q.size(); t; --t) { auto [i, j] = q.front(); q.pop(); if (++j == words[i].size()) ++ans; else d[words[i][j] - 'a'].emplace(i, j); } } return ans; } }; // Solution 3 class Solution { public: int numMatchingSubseq(string s, vector<string>& words) { vector<vector<int>> d(26); for (int i = 0; i < s.size(); ++i) d[s[i] - 'a'].emplace_back(i); int ans = 0; auto check = [&](string& w) { int i = -1; for (char& c : w) { auto& t = d[c - 'a']; int j = upper_bound(t.begin(), t.end(), i) - t.begin(); if (j == t.size()) return false; i = t[j]; } return true; }; for (auto& w : words) ans += check(w); return ans; } }; -
class Solution: def numMatchingSubseq(self, s: str, words: List[str]) -> int: d = defaultdict(deque) for i, w in enumerate(words): d[w[0]].append((i, 0)) ans = 0 for c in s: for _ in range(len(d[c])): i, j = d[c].popleft() j += 1 if j == len(words[i]): ans += 1 else: d[words[i][j]].append((i, j)) return ans # Solution 2 class Solution: def numMatchingSubseq(self, s: str, words: List[str]) -> int: d = defaultdict(deque) for i, w in enumerate(words): d[w[0]].append((i, 0)) ans = 0 for c in s: for _ in range(len(d[c])): i, j = d[c].popleft() j += 1 if j == len(words[i]): ans += 1 else: d[words[i][j]].append((i, j)) return ans # Solution 3 class Solution: def numMatchingSubseq(self, s: str, words: List[str]) -> int: def check(w): i = -1 for c in w: j = bisect_right(d[c], i) if j == len(d[c]): return False i = d[c][j] return True d = defaultdict(list) for i, c in enumerate(s): d[c].append(i) return sum(check(w) for w in words) -
func numMatchingSubseq(s string, words []string) (ans int) { type pair struct{ i, j int } d := [26][]pair{} for i, w := range words { d[w[0]-'a'] = append(d[w[0]-'a'], pair{i, 0}) } for _, c := range s { q := d[c-'a'] d[c-'a'] = nil for _, p := range q { i, j := p.i, p.j+1 if j == len(words[i]) { ans++ } else { d[words[i][j]-'a'] = append(d[words[i][j]-'a'], pair{i, j}) } } } return } // Solution 2 func numMatchingSubseq(s string, words []string) (ans int) { type pair struct{ i, j int } d := [26][]pair{} for i, w := range words { d[w[0]-'a'] = append(d[w[0]-'a'], pair{i, 0}) } for _, c := range s { q := d[c-'a'] d[c-'a'] = nil for _, p := range q { i, j := p.i, p.j+1 if j == len(words[i]) { ans++ } else { d[words[i][j]-'a'] = append(d[words[i][j]-'a'], pair{i, j}) } } } return } // Solution 3 func numMatchingSubseq(s string, words []string) (ans int) { d := [26][]int{} for i, c := range s { d[c-'a'] = append(d[c-'a'], i) } check := func(w string) bool { i := -1 for _, c := range w { t := d[c-'a'] j := sort.SearchInts(t, i+1) if j == len(t) { return false } i = t[j] } return true } for _, w := range words { if check(w) { ans++ } } return }