算法-滑动窗口-串联所有单词的子串
1 题目概述
1.1 题目出处
https://leetcode.cn/problems/substring-with-concatenation-of-all-words/
1.2 题目描述
2 滑动窗口+Hash表
2.1 解题思路
- 构建一个大小为串联子串的总长的滑动窗口
- 为每个words中的子串创建一个hash表, <子串值,子串出现次数>
- 记录每次开始位置,从左往右遍历字符串s,每次截取words[0]长度的子串,和2中hash表对比,如果没有或者使用次数超限,就表示该组合无法构成,退出该次检测;否则继续检测,直到构成的串联子串长度满足要求或者已检测长度超限。构建成功时,记录下起始序号
- 一轮检测完成后,窗口向右滑动1个长度,继续下一轮检测
2.2 代码
class Solution {
public List<Integer> findSubstring(String s, String[] words) {
List<Integer> resultList = new ArrayList<>();
int windowSize = words[0].length();
if (windowSize > s.length()) {
return resultList;
}
int strLength = windowSize * words.length;
if (strLength > s.length()){
return resultList;
}
Map<String, Integer> wordMap = new HashMap<>();
for (int i = 0; i < words.length; i++) {
Integer subKeyTimes = wordMap.get(words[i]);
if (null == subKeyTimes) {
subKeyTimes = 0;
}
wordMap.put(words[i], subKeyTimes + 1);
}
for (int i = 0; i <= s.length() - strLength; i++) {
int result = getStart(s, words, strLength, windowSize, i, wordMap);
if (result != -1) {
resultList.add(result);
}
}
return resultList;
}
private int getStart(String s, String[] words, int strLength, int windowSize, int first, Map<String, Integer> wordMap) {
int start = -1;
int length = 0;
Map<String, Integer> useMap = new HashMap();
for (int i = first; i < s.length() && length < strLength && i < first + strLength; i += windowSize) {
String sub = s.substring(i, i + windowSize);
Integer subKeyTimes = wordMap.get(sub);
if (null == subKeyTimes) {
return -1;
}
Integer useTimes = useMap.get(sub);
if (null == useTimes) {
useTimes = 1;
} else {
useTimes += 1;
}
if (useTimes > subKeyTimes) {
return -1;
}
useMap.put(sub, useTimes);
length += windowSize;
if (start == -1) {
start = i;
}
}
if (length == strLength) {
return start;
}
return -1;
}
}
2.3 时间复杂度
s.length=N,words.length=M ,时间复杂度O(N*M)
2.4 空间复杂度
O(M)
3 双滑动窗口+Hash表
3.1 解题思路
使用两个滑动窗口:
- 外层滑动窗口,每次移动大小为1,最多移动words[0]长度
- 内层滑动窗口,每次移动大小为words[0]
相当于把words[0]长度倍数的子串放在一起,用内层滑动窗口进行解析:
- 如果遇到当前子串不在目标字符串s,则舍弃之前记录,从下一个子串开始
- 如果遇到当前子串在目标字符串s内:
- 使用次数未超限,则记录使用次数、首个拼接子串下标、并累加拼接长度,再看是否已经拼接目标串联子串成功:
- 如果成功则记录下标并移除首个子串,继续检测下一个子串;
- 如果未成功,直接继续继续检测下一个子串;
- 使用次数超限,则不断移除已拼接子串内首个子串,直到次数不超限为止,并继续检测下一个子串
- 使用次数未超限,则记录使用次数、首个拼接子串下标、并累加拼接长度,再看是否已经拼接目标串联子串成功:
3.2 代码
class Solution {
public List<Integer> findSubstring(String s, String[] words) {
List<Integer> resultList = new ArrayList<>();
int windowSize = words[0].length();
if (windowSize > s.length()) {
return resultList;
}
int strLength = windowSize * words.length;
if (strLength > s.length()){
return resultList;
}
Map<String, Integer> wordMap = new HashMap<>();
for (int i = 0; i < words.length; i++) {
Integer subKeyTimes = wordMap.get(words[i]);
if (null == subKeyTimes) {
subKeyTimes = 0;
}
wordMap.put(words[i], subKeyTimes + 1);
}
for (int i = 0; i < windowSize; i += 1) {
List<Integer> subResultList = getStart(s, strLength, windowSize, i, wordMap);
if (subResultList.size() > 0) {
resultList.addAll(subResultList);
}
}
return resultList;
}
private List<Integer> getStart(String s, int strLength, int windowSize, int first, Map<String, Integer> wordMap) {
List<Integer> resultList = new ArrayList();
int start = -1;
int length = 0;
Map<String, Integer> useMap = new HashMap();
for (int i = first; i <= s.length() - windowSize; i += windowSize) {
String sub = s.substring(i, i + windowSize);
Integer subKeyTimes = wordMap.get(sub);
if (null == subKeyTimes) {
// 子串找不到,清空useMap
useMap.clear();
start = -1;
length = 0;
continue;
}
length += windowSize;
Integer useTimes = useMap.get(sub);
if (null == useTimes) {
useTimes = 1;
} else {
useTimes += 1;
useMap.put(sub, useTimes);
while (useTimes > subKeyTimes) {
// 子串使用次数超出存在的子串条数,删除第一个子串计数,直到不超出为止
String firstSub = s.substring(start, start + windowSize);
useMap.put(firstSub, useMap.get(firstSub) - 1);
useTimes = useMap.get(sub);
start = start + windowSize;
length -= windowSize;
}
}
useMap.put(sub, useTimes);
if (start == -1) {
start = i;
}
if (length == strLength) {
// 成功凑成一个串联子串
// 记录开始下标
resultList.add(start);
// 移除首个子串
String firstSub = s.substring(start, start + windowSize);
useMap.put(firstSub, useMap.get(firstSub) - 1);
start = start + windowSize;
length -= windowSize;
}
}
return resultList;
}
}
3.3 时间复杂度
s.length=N,words.length=M ,时间复杂度O(N)
3.4 空间复杂度
O(M)
参考文档
- 详细通俗的思路分析,多解法