You are given a string, s, and a list of words, words, that are all of the same length. Find all starting indices of substring(s) in s that is a concatenation of each word in words exactly once and without any intervening characters.
Example 1:
Input:
s = "barfoothefoobarman",
words = ["foo","bar"]
Output: [0,9]
Explanation: Substrings starting at index 0 and 9 are "barfoor" and "foobar" respectively.
The output order does not matter, returning [9,0] is fine too.
Example 2:
Input:
s = "wordgoodstudentgoodword",
words = ["word","student"]
Output: []
Solution: Mark the occurence of each word in words into a hashmap, for each i, keep checking the following len(word)'s string word occured or not, if no , go for next i(Notice that substring, exactly once), if yes, decrease occurence of the string word 1, when occurence of all words is 0, then add current i to result list.
Key point:
1. end point of i
2. end point of j
Code: 170 ms, Time complexity O(N * k), N is the length of given string s; k is the length of words.
public List<Integer> findSubstring(String s, String[] words) {
List<Integer> res = new ArrayList<>();
if (s == null || s.length() == 0 || words == null || words.length == 0) {
return res;
}
// mark occurence of each word in words
Map<String, Integer> hashMap = new HashMap<>();
for (String ss : words) {
hashMap.put(ss, hashMap.getOrDefault(ss, 0) + 1);
}
int len = words[0].length();
for (int i = 0; i <= s.length() - len * words.length; i++) {
Map<String, Integer> map = new HashMap<>(hashMap);
int j = i;
while (j + len <= s.length()) {
String cur = s.substring(j, j + len);
if (!map.containsKey(cur)) {
break;
} else {
j += len;
map.put(cur, map.get(cur) - 1);
if (map.get(cur) == 0) {
map.remove(cur);
}
if (map.isEmpty()){
res.add(i);
}
}
}
}
return res;
}
Solution from leetcode discuss:
There could be several starting index i in string s which meet the needs. We can find these substrings by maintaining sliding window. Match the words in substring with given input words array. Iterate by words instead of by character.
To be more effective, in the example, given string s has length 18, word in words has length 3. So first time we cut s into new words with each having index 0, 3, 6, 9, 12, 15. Second time, we cut s into new words with each having index 1, 4, 7, 10, 13, 16. Third time, we cut s into new words with each having index 2, 5, 8, 11, 14, 17. Then we cover every cases. Each time we have a list of new words, then we maintain a substring by using a sliding window --- add word into right side of sliding window when we don't have enough words in substring conpared with number of words in given words array, --- remove left side of word when we have extra word in substring. Every time after adding word or removing word, check if current match given words array. So we need to pre compute the number of distinct words, if the occurrence time of a word equals to occurence in given words, then matched word count++. If count == number of distinct words in given words array, then i --- left side of sliding window --- is added in result list. Time complexity: we have len times loop, each loop, we have N / len words, a word is visited by at most 2 times. So len * 2 * N/len = O(N), N is the length of given string s.
Code: 22 ms; time complexity O(n)
public List<Integer> findSubstring(String s, String[] words) {
List<Integer> res = new ArrayList<>();
if (words == null || words.length == 0) {
return res;
}
int distinctWordCount = 0;
Map<String, Integer> wordDict = new HashMap<>();
for (String word : words) {
wordDict.put(word, wordDict.getOrDefault(word, 0) + 1);
if (wordDict.get(word) == 1) {
distinctWordCount++;
}
}
int len = words[0].length();
for (int k = 0; k < len; k++) {
Map<String, Integer> map = new HashMap<>();
int count = 0;
for (int i = k, j = k; j <= s.length() - len; j += len){
if ((j - i) / len >= words.length) {
String rmWord = s.substring(i, i + len);
if (wordDict.containsKey(rmWord)) {
map.put(rmWord, map.get(rmWord) - 1);
if (map.get(rmWord) == wordDict.get(rmWord) - 1) {
count--;
}
}
i += len;
}
String addWord = s.substring(j, j + len);
if (wordDict.containsKey(addWord)) {
map.put(addWord, map.getOrDefault(addWord, 0) + 1);
if (map.get(addWord) == wordDict.get(addWord)) {
count++;
}
}
if (count == distinctWordCount) {
res.add(i);
}
}
}
return res;
}