问题:
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).
问题的要求是:
字符串数组words中所有的字符串长度相等。
将words中所有的字符串按照任意顺序拼接在一起,得到所有可能的连续字符串。
如果指定字符串s包含这些连续字符串,那么返回这些连续字符串出现的起始下标组成的集合。
代码示例:
1、暴力解法
可行,但最后会Time Limit Exceeded。
public class Solution {
public List<Integer> findSubstring(String s, String[] words) {
List<Integer> result = new ArrayList<>();
if (s == null || s.length() < 1
|| words == null || words.length < 1) {
return result;
}
//得到words中,每个字符串出现的次数
Map<String, Integer> wordNum = new HashMap<>();
for (String word : words) {
wordNum.put(word,
wordNum.containsKey(word) ? wordNum.get(word) + 1 : 1);
}
//这里的思路是:不断递增起始位置
//从起始位置开始,不断判断下一个规定长度的子串是否在words数组中
//如果下一个子串,不在words数组中,那么显然本次的起始下标不是我们需要的
for (int i = 0; i <= s.length() - words.length * words[0].length(); ++i) {
Map<String, Integer> newMap = new HashMap<>(wordNum);
for (int j = 0; j < words.length; ++j) {
String str = s.substring(i + j * words[0].length(), i + (j+1) * words[0].length());
//因为可能有重复的子串,因此上面首先得到了words中每个字符串的数量
if (newMap.containsKey(str)) {
int num = newMap.get(str);
if (num == 1) {
newMap.remove(str);
} else {
newMap.put(str, num - 1);
}
//如果连续找到了所有words中的字符串,
//那么这次就是一个正确的下标
if (newMap.isEmpty()) {
result.add(i);
break;
}
} else {
break;
}
}
}
return result;
}
}
整体来讲,暴力解法的思路还是很好理解的。
但是由于没有充分利用前一次的匹配结果,导致有很多重复的匹配过程,因此最后会超时。
例如:
s为”abcabcdefabcabcabc”, words为”abc”、”abc”、”abc”
当从s的第一个a开始匹配时,发现def不匹配,因此找不到下标;
等匹配到第二个a时,仍会因为def不匹配找不到下标。
因此,对于从a开始的匹配,当发现def不匹配时,就应该从def之后的第一个a开始重新匹配。
2、滑动窗口
public class Solution {
public List<Integer> findSubstring(String s, String[] words) {
List<Integer> result = new ArrayList<>();
if (s == null || s.length() < 1
|| words == null || words.length < 1) {
return result;
}
int wordLen = words[0].length();
int numWord = words.length;
int windowLen = wordLen * numWord;
int sLen = s.length();
if (windowLen > s.length()) {
return result;
}
Map<String, Integer> map = new HashMap<>();
for (String word : words) {
map.put(word, map.getOrDefault(word, 0) + 1);
}
//定义一个滑动窗口,长度为windowLen
//不断移动左、右下标,每次增加的步长为wordLen
//于是不重复的起始位置为0~wordLen-1
for (int i = 0; i < wordLen; ++i) {
//curMap用于维护当前窗口内的字符串信息
HashMap<String, Integer> curMap =new HashMap<>();
int count = 0;
for (int left = i, right = i; right + wordLen <= sLen; right += wordLen) {
if (left + windowLen > sLen) {
break;
}
String word = s.substring(right, right + wordLen);
//map中没有这个字符串,修改left位置
if (!map.containsKey(word)) {
curMap.clear();
count = 0;
left = right + wordLen;
} else {
//达到最大窗口了,修改左下标
if (right == left + windowLen) {
String preWord = s.substring(left, left + wordLen);
int num = curMap.get(preWord);
if (num == 1) {
curMap.remove(preWord);
} else {
curMap.put(preWord, num - 1);
if (num - 1 >= map.get(preWord)) {
--count;
}
}
left = left + wordLen;
}
curMap.put(word, curMap.getOrDefault(word, 0) + 1);
if (curMap.get(word) > map.get(word)) {
count++;
}
if (count == 0 && left + windowLen == right + wordLen) {
result.add(left);
}
}
}
}
return result;
}
}