原题链接:https://leetcode.com/problems/substring-with-concatenation-of-all-words/description/
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:[]
利用哈希表和滑动窗口可以以O(n)的时间复杂度解决问题。
思路:将words数组中的字符串存入hashmap,值为出现次数。然后开始遍历字符串s,外层循环从0到word[0].length-1,内层循环从i到最后(需判断最后是否越界),内层循环每次增加一个word的长度,这样外层循环完毕后刚好遍历完所有情况。(注意,words中的每一个word长度相同,所以才可以这样遍历)
维持一个滑动窗口,左右边界都只向右移动,每次先移动right,窗口大小表示匹配到的word个数,随着移动而增大或减小。移动right,如果map中存在当前字符串,修改map中对应word的个数,一旦发现当前需要修改的个数已经为0,说明出现了多余的字符串,窗口需要更新,这时候开始移动窗口左边界,直到左边界滑过与当前字符串相同的字符串,滑动左边界的同时把map中对应字符串的个数加回来。 如果当前map中找不到cur(当前字符串),说明cur之前的窗口已经没用了,重置窗口大小为0,将left到right的map值加回来,然后从下一个位置重新开始匹配。
当窗口大小与words长度相同时,说明s中从left到right+wLen这一部分刚好可以匹配words,将left存入list即可。
多说无益,代码如下:
public List<Integer> findSubstring(String s, String[] words) {
List<Integer> list = new ArrayList<Integer>();
if (s == null || s.length() == 0 || words == null || words.length == 0)
return list;
int sLen = s.length(), wLen = words[0].length(), wsLen = words.length;
if (sLen < wLen * wsLen)
return list;
Map<String, Integer> map = new HashMap<>(); // 记录words中每一个字符串的数目
for (String word : words) {
if (map.containsKey(word))
map.put(word, map.get(word) + 1);
else
map.put(word, 1);
}
for (int i = 0; i < wLen; i++) {
int left = i, right = i, window = 0; // 窗口的大小和左右边界
while (right + (wsLen - window) * wLen <= sLen && right + wLen <= sLen) {
String cur = s.substring(right, right + wLen); // right位置的字符串
if (map.containsKey(cur)) {
int cnt = map.get(cur); // 当前字符串的个数
window++; // 包含当前字符串,窗口大小+1
if (cnt > 0) {
map.put(cur, cnt - 1);
} else { // map当前字符串个数为0,说明出现重复字符串
String removed = s.substring(left, left + wLen); // 从窗口左边开始移除字符串
while (!removed.equals(cur)) {
map.put(removed, map.get(removed) + 1); // 恢复移除字符串的个数
left += wLen;
window--;
removed = s.substring(left, left + wLen);
}
left += wLen;
window--;
}
if (window == wsLen) // 窗口大小等于数组长度,匹配成功
list.add(left);
} else {
// 恢复map
window = 0;
while (left < right) {
String removed = s.substring(left, left + wLen); // 从窗口左边开始移除字符串
map.put(removed, map.get(removed) + 1); // 恢复移除字符串的个数
left += wLen;
}
left += wLen; // 左边跳过当前这一位不匹配的字符串
}
right += wLen; // 窗口往右拓展一个字符串的长度
}
// 恢复map
while (left < right) {
String removed = s.substring(left, left + wLen); // 从窗口左边开始移除字符串
map.put(removed, map.get(removed) + 1); // 恢复移除字符串的个数
left += wLen;
}
}
return list;
}