Given string S
and a dictionary of words words
, find the number of words[i]
that is a subsequence of S
.
Example : Input: S = "abcde" words = ["a", "bb", "acd", "ace"] Output: 3 Explanation: There are three words inwords
that are a subsequence ofS
: "a", "acd", "ace".
Note:
- All words in
words
andS
will only consists of lowercase letters. - The length of
S
will be in the range of[1, 50000]
. - The length of
words
will be in the range of[1, 5000]
. - The length of
words[i]
will be in the range of[1, 50]
.
Idea1. Bruteforce, for each word, scan the string S to see if the word is a subseq.
Time complexity: O(S.length()* O(words.length*word.length()), like O(n*m)
Space complexity: O(1)
1 class Solution { 2 boolean isSubseq(String S, String word){ 3 for(int i = 0, j = 0; j < S.length(); ++j) { 4 if(S.charAt(j) == word.charAt(i)) { 5 ++i; 6 if(i == word.length()) { 7 return true; 8 } 9 } 10 } 11 12 return false; 13 } 14 public int numMatchingSubseq(String S, String[] words) { 15 int count = 0; 16 for(String word: words) { 17 if(isSubseq(S, word)) { 18 ++count; 19 } 20 } 21 return count; 22 } 23 }
Idea 2. Store the prefix char for each words, scan string S only once, it's linear
Time complexity: O(n + m)
Space complexity: O(1)
use map to store pair(char, {{words index, next char index} }) for scanning next character, scan each word only once to build the map
1 class Solution { 2 public int numMatchingSubseq(String S, String[] words) { 3 Map<Character, List<List<Integer>>> wordsPosition = new HashMap<>(); 4 5 for(int i = 0; i < words.length; ++i) { 6 wordsPosition.computeIfAbsent(words[i].charAt(0), (key -> new ArrayList<>())).add(Arrays.asList(i, 1)); 7 } 8 9 int result = 0; 10 for(int i = 0; i < S.length(); ++i) { 11 char c = S.charAt(i); 12 13 List<List<Integer>> copy = new ArrayList<>(wordsPosition.getOrDefault(c, Collections.emptyList())); 14 wordsPosition.remove(c); 15 16 for(List<Integer> position: copy) { 17 int wordIndex = position.get(0); 18 int charIndex = position.get(1); 19 if(charIndex == words[wordIndex].length()) { 20 ++result; 21 } 22 else { 23 wordsPosition.computeIfAbsent(words[wordIndex].charAt(charIndex), (key -> new ArrayList<>())).add(Arrays.asList(wordIndex, charIndex+1)); 24 } 25 } 26 } 27 28 return result; 29 } 30 }
use String CharacterIterator, save the need to store wordIndex, we can locate the next char with CharacterIterator only.
1 import java.text.StringCharacterIterator; 2 import java.text.CharacterIterator; 3 4 class Solution { 5 public int numMatchingSubseq(String S, String[] words) { 6 int len = 128; 7 List<List<StringCharacterIterator>> wordsPosition = new ArrayList<>(); 8 for(int i = 0; i < len; ++i) { 9 wordsPosition.add(new ArrayList<>()); 10 } 11 12 13 for(int i = 0; i < words.length; ++i) { 14 wordsPosition.get(words[i].charAt(0) - 'a').add(new StringCharacterIterator(words[i])); 15 } 16 17 int result = 0; 18 for(int i = 0; i < S.length(); ++i) { 19 int c = S.charAt(i) - 'a'; 20 21 List<StringCharacterIterator> copy = new ArrayList<>(wordsPosition.get(c)); 22 23 wordsPosition.get(c).clear(); 24 25 for(StringCharacterIterator iter: copy) { 26 char next = iter.next(); 27 if(next == CharacterIterator.DONE) { 28 ++result; 29 } 30 else { 31 wordsPosition.get(next - 'a').add(iter); 32 } 33 } 34 } 35 36 return result; 37 } 38 }