题目:
给定一个字符串 s 和一些 长度相同 的单词 words 。找出 s 中恰好可以由 words 中所有单词串联形成的子串的起始位置。
注意子串要与 words 中的单词完全匹配,中间不能有其他字符 ,但不需要考虑 words 中单词串联的顺序。
示例 1:
输入:s = "barfoothefoobarman", words = ["foo","bar"]
输出:[0,9]
解释:
从索引 0 和 9 开始的子串分别是 "barfoo" 和 "foobar" 。
输出的顺序不重要, [9,0] 也是有效答案。
示例 2:
输入:s = "wordgoodgoodgoodbestword", words = ["word","good","best","word"]
输出:[]
示例 3:
输入:s = "barfoofoobarthefoobarman", words = ["bar","foo","the"]
输出:[6,9,12]
思路:
1. 滑动窗口解决方法,使用i,j记录当前下标
2. 由于所有字符串的长度一致,所以滑动窗口的步长即为words数组内任一单词的长度
3. 由于字符列表中可能会出现重复字符,所以用比较笨蛋的方法就是,我们对于每个窗口定义一个remain list,除去当前窗口的单词后,看remain list中是否为空,如果不为空,则当前窗口不是连续单词
4. 滑动窗口的具体思路:
1)设置i为起点,j为终点,步长为单词长,为了遍历所有可能性方案, i的取值为0到单词长度-1
2)使用队列记录窗口内的单词,如果需要向后滑动i,则取出队列头;如果需要向后滑动j,则放入队列尾;
3)滑动中的判断:
a. 如果该单词存在于列表,并且存在于remain list中,则j向后滑动,remain list去除该单词,队列加入该单词
b. 如果发现该单词存在于列表中,但是不存在于remain list中,说明该单词在前面字符中已经存在,从队列中取出元素(同时在remain list中加入取出的单词),直到取出的单词和当前单词一致为止,重复和a一致的操作
c. 如果该单词不存在于列表中,则证明该单词不能包含于滑动窗口中,将滑动窗口一整个向后滑动,i的指针指向该单词的后面一位
代码:
public List<Integer> findSubstring(String s, String[] words) {
List<Integer> res = new ArrayList<>();
List<String> wl = Arrays.asList(words);
int length = words[0].length();
for (int k = 0; k < length; k++) {
int i = k;
int j = i - 1;
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<String>(words.length);
Set<Integer> last = new HashSet<>();
List<String> remained = new ArrayList<>(Arrays.asList(words));
while (j < s.length()) {
if (queue.size() == words.length) {
res.add(i);
i += length;
String ind = queue.poll();
//last.remove(ind);
remained.add(ind);
}
if (j + 1 + length > s.length()) {
break;
}
String st = s.substring(j + 1, j + 1 + length);
if (wl.contains(st)) {
if (!remained.contains(st)) {
while (true) {
String ind = queue.poll();
remained.add(ind);
i += length;
if (st.equals(ind)) {
break;
}
}
}
remained.remove(st);
queue.add(st);
j += length;
} else {
i = j + 1 + length;
j = i - 1;
remained = new ArrayList<>(Arrays.asList(words));
queue.clear();
}
}
}
return res;
}