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.
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;
}