For example, given:
s: "barfoothefoobarman"
words: ["foo", "bar"]
You should return the indices: [0,9].
(order does not matter).
[分析] 方法2的分析转载在 http://segmentfault.com/a/1190000002625580
使用滑动窗口思想,始终保持words 集合中的单词在窗口中仅出现一次,变量count记录窗口中包含words集合中单词的个数,当count == words中单词个数时即找到一个符合条件的子串。
需要使用的内存空间:
1 两张HashMap, dict & currMap, 分别保存words中的单词和窗口中包含的单词,key为单词,value是单词出现个数
2 count, 记录窗口中包含words集合中单词的个数
3 left, 滑动窗口的左边界
实现的步骤:
1 遍历words,构建dict
2 以单词长度为步长,遍历字符串,若当前单词在dict中,则加入或者更新currMap, 并进入步骤3,否则清空currMap和count并且更新left到下一个单词处
3 检查当前单词在窗口中的出现次数是否超过额定值,若超过从左边收缩窗口,收缩方法:从当前左边界开始从currMap中删除单词并递减count直到遇到当前单词(也要删除)。
5 检查count是否 等于words集合单词数,若等于将当前窗口左边界加入结果,更新左边界和currMap、count
这里解释下步骤4中的收缩滑块,这是因为当前滑块中有单词的出现次数超过了额定的出现次数,那么就是需要收缩滑块来剔除这个单词,相当于是从滑块的左起点开始寻找该单词,找到之后,将该单词的右端点作为滑块新的左起点,这样就保证了滑块中所有单词都是小于等于额定出现次数,这样也保证了count计数的有效性。
遇到总单词表中不存在的单词的情况,在步骤2中已经说明,清空当前数据之后继续循环,也就是保证了滑块中是不会出现不存在单词表中的单词的。
最后,考虑最外圈循环,如果是从0开始作为滑块的初始起点,那么其实并没有遍历字符串中的所有可能子串,因为步长是单词长度,所以移动滑块的时候会跨过很多可能子串,所以要在外圈再加一层循环,这个循环的作用就是移动滑块的初始起点,所以循环次数就是单词的长度。
方法1 没有使用滑动窗口思想,会有重复计算,时间复杂度是O(n * m), n为目标字符串长度,m为words集合大小,因此效率不如方法2,其时间复杂度为O(n)
// method 2: sliding window public List<Integer> findSubstring(String s, String[] words) { List<Integer> result = new ArrayList<Integer>(); if (s == null || s.length() == 0 || words == null || words.length == 0) return result; int m = words.length, n = s.length(); int wordLen = words[0].length(); HashMap<String, Integer> dict = new HashMap<String, Integer>(); for (int i = 0; i < m; i++) { if (!dict.containsKey(words[i])) dict.put(words[i], 1); else dict.put(words[i], dict.get(words[i]) + 1); } for (int i = 0; i < wordLen; i++) { HashMap<String, Integer> currMap = new HashMap<String, Integer>(); int count = 0; int left = i; for (int j = i; j + wordLen <= n; j += wordLen) { String currWord = s.substring(j, j + wordLen); if (dict.containsKey(currWord)) { if (currMap.containsKey(currWord)) { currMap.put(currWord, currMap.get(currWord) + 1); } else { currMap.put(currWord, 1); } count++; if (currMap.get(currWord) > dict.get(currWord)) { // shrinkage left edge of window while (true) { String tmp = s.substring(left, left + wordLen); currMap.put(tmp, currMap.get(tmp) - 1); left += wordLen; count--; if (tmp.equals(currWord)) break; } } // found a valid window if (count == m) { result.add(left); String leftWord = s.substring(left, left + wordLen); currMap.put(leftWord, currMap.get(leftWord) - 1); count--; left += wordLen; } } else { // find a word which is not in dict, reset all currMap.clear(); count = 0; left = j + wordLen; } } } return result; } // method 1 public List<Integer> findSubstring1(String s, String[] words) { List<Integer> result = new ArrayList<Integer>(); if (s == null || s.length() == 0 || words == null || words.length == 0) return result; int m = words.length, n = s.length(); int singleLen = words[0].length(); int targetLen = m * singleLen; HashMap<String, Integer> dict = new HashMap<String, Integer>(); for (int i = 0; i < m; i++) { if (!dict.containsKey(words[i])) dict.put(words[i], 1); else dict.put(words[i], dict.get(words[i]) + 1); } int end = n - targetLen; for (int i = 0; i <= end; i++) { HashMap<String, Integer> map = new HashMap<String, Integer>(dict); int j = i; while (j < i + targetLen) { String curr = s.substring(j, j + singleLen); Integer val = map.get(curr); if ( val != null && val > 0) { map.put(curr, val - 1); j += singleLen; } else { break; } } if (j == i + targetLen) result.add(i); } return result; }