Skip to content

792. Number of Matching Subsequences 👍

Approach 1: Trie

  • Time: $O(|\texttt{S}| + \Sigma |\texttt{words[i]}|)$
  • Space: $O(\Sigma |\texttt{words[i]}|)$
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
struct TrieNode {
  vector<TrieNode*> children;
  int count = 0;
  TrieNode() : children(26) {}
  ~TrieNode() {
    for (TrieNode* child : children)
      delete child;
  }
};

class Solution {
 public:
  int numMatchingSubseq(string S, vector<string>& words) {
    for (const string& word : words)
      insert(word);

    return dfs(S, 0, &root);
  }

 private:
  TrieNode root;

  void insert(const string& word) {
    TrieNode* node = &root;
    for (const char c : word) {
      const int i = c - 'a';
      if (!node->children[i])
        node->children[i] = new TrieNode;
      node = node->children[i];
    }
    ++node->count;
  }

  int dfs(const string& s, int i, TrieNode* node) {
    int ans = node->count;
    if (i >= s.length())
      return ans;

    for (int j = 0; j < 26; ++j)
      if (node->children[j]) {
        const int index = s.find('a' + j, i);
        if (index != -1)
          ans += dfs(s, index + 1, node->children[j]);
      }

    return ans;
  }
};
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class TrieNode {
  public TrieNode[] children = new TrieNode[26];
  public int count = 0;
}

class Solution {
  public int numMatchingSubseq(String S, String[] words) {
    for (final String word : words)
      insert(word);

    return dfs(S, 0, root);
  }

  private TrieNode root = new TrieNode();

  private void insert(final String word) {
    TrieNode node = root;
    for (final char c : word.toCharArray()) {
      final int i = c - 'a';
      if (node.children[i] == null)
        node.children[i] = new TrieNode();
      node = node.children[i];
    }
    ++node.count;
  }

  private int dfs(final String s, int i, TrieNode node) {
    int ans = node.count;
    if (i >= s.length())
      return ans;

    for (int j = 0; j < 26; ++j)
      if (node.children[j] != null) {
        final int index = s.indexOf('a' + j, i);
        if (index != -1)
          ans += dfs(s, index + 1, node.children[j]);
      }

    return ans;
  }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Solution:
  def numMatchingSubseq(self, s: str, words: List[str]) -> int:
    root = {}

    def insert(word: str) -> None:
      node = root
      for c in word:
        if c not in node:
          node[c] = {'count': 0}
        node = node[c]
      node['count'] += 1

    for word in words:
      insert(word)

    def dfs(s: str, i: int, node: dict) -> int:
      ans = node['count'] if 'count' in node else 0

      if i >= len(s):
        return ans

      for c in string.ascii_lowercase:
        if c in node:
          try:
            index = s.index(c, i)
            ans += dfs(s, index + 1, node[c])
          except ValueError:
            continue

      return ans

    return dfs(s, 0, root)

Approach 2: Special

  • Time: $O(|\texttt{S}| + \Sigma |\texttt{word[i]}|)$
  • Space: $O(|\texttt{words}|)$
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Solution {
 public:
  int numMatchingSubseq(string s, vector<string>& words) {
    int ans = 0;
    // pair (i, j) := words[i] and the character words[i][j] waiting for
    vector<vector<pair<int, int>>> bucket(26);

    // for each word, it's waiting for word[0]
    for (int i = 0; i < words.size(); ++i)
      bucket[words[i][0] - 'a'].emplace_back(i, 0);

    for (const char c : s) {
      // let prevBucket = bucket[c] and clear bucket[c]
      vector<pair<int, int>> prevBucket;
      swap(prevBucket, bucket[c - 'a']);
      for (auto& [i, j] : prevBucket)
        if (++j == words[i].length())  // all characters in words[i] are matched
          ++ans;
        else
          bucket[words[i][j] - 'a'].emplace_back(i, j);
    }

    return ans;
  }
};
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Solution {
  public int numMatchingSubseq(String s, String[] words) {
    int ans = 0;
    // pair (i, j) := words[i] and the character words[i][j] waiting for
    List<Pair<Integer, Integer>>[] bucket = new List[26];

    for (int i = 0; i < 26; ++i)
      bucket[i] = new ArrayList<>();

    // for each word, it's waiting for word[0]
    for (int i = 0; i < words.length; ++i)
      bucket[words[i].charAt(0) - 'a'].add(new Pair<>(i, 0));

    for (final char c : s.toCharArray()) {
      // let prevBucket = bucket[c] and clear bucket[c]
      List<Pair<Integer, Integer>> prevBucket = bucket[c - 'a'];
      bucket[c - 'a'] = new ArrayList<>();
      for (var pair : prevBucket) {
        final int i = pair.getKey();
        final int j = pair.getValue() + 1;
        if (j == words[i].length()) // all characters in words[i] are matched
          ++ans;
        else
          bucket[words[i].charAt(j) - 'a'].add(new Pair<>(i, j));
      }
    }

    return ans;
  }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution:
  def numMatchingSubseq(self, s: str, words: List[str]) -> int:
    ans = 0
    # pair (i, j) := words[i] and the character words[i][j] waiting fo
    bucket = [[] for _ in range(26)]

    # for each word, it's waiting for word[0]
    for i, word in enumerate(words):
      bucket[ord(word[0]) - ord('a')].append((i, 0))

    for c in s:
      # let prevBucket = bucket[c] and clear bucket[c]
      index = ord(c) - ord('a')
      prevBucket = bucket[index]
      bucket[index] = []
      for i, j in prevBucket:
        j += 1
        if j == len(words[i]):  # all characters in words[i] are matched
          ans += 1
        else:
          bucket[ord(words[i][j]) - ord('a')].append((i, j))

    return ans