30. 串联所有单词的子串 给定一个字符串 s 和一个字符串数组 words。 words 中所有字符串 长度相同。 s 中的 串联子串 是指一个包含 words 中所有字符串以任意顺序排列连接起来的子串。 例如,如果 words = ["ab","cd","ef"], 那么 "abcdef", "abefcd","cdabef", "cdefab","efabcd", 和 "efcdab" 都是串联子串。 "acdbef" 不是串联子串,因为他不是任何 words 排列的连接。 返回所有串联子串在 s 中的开始索引。你可以以 任意顺序 返回答案。 示例 1: 输入:s = "barfoothefoobarman", words = ["foo","bar"] 输出:[0,9] 解释:因为 words.length == 2 同时 words[i].length == 3, 连接的子字符串的长度必须为 6。 子串 "barfoo" 开始位置是 0。它是 words 中以 ["bar","foo"] 顺序排列的连接。 子串 "foobar" 开始位置是 9。它是 words 中以 ["foo","bar"] 顺序排列的连接。 输出顺序无关紧要。返回 [9,0] 也是可以的。 示例 2: 输入:s = "wordgoodgoodgoodbestword", words = ["word","good","best","word"] 输出:[] 解释:因为 words.length == 4 并且 words[i].length == 4, 所以串联子串的长度必须为 16。 s 中没有子串长度为 16 并且等于 words 的任何顺序排列的连接。 所以我们返回一个空数组。
题解:
介绍动态窗口
滑动窗口考察的是「综合能力」
窗口的数据结构:应根据各种数据结构的特点来选取
哈希表,unordered_map
优先队列,priority_queue
红黑树,multiset、set、multimap、map
单调队列,deque实现
应用条件:
原数据必须满足单调性,不满足的可以用前缀和。例如力扣 560. 和为 K 的子数组原数据中含有负数,破坏了单调性
模板 (c++)
void slidingWindow(string s, string t) {
int left = 0;
int right = 0;
int valid = 0; // 窗口内已凑齐的字符种类数量
unordered_map<char, int> window;
unordered_map<char, int> need; // 需要凑齐的字符和对应数量
for (char c : t) need[c]++;
while (right < s.size()) {
// 右边届入窗口,进行窗口内数据的一系列更新
window[s[right]]++;
if (window[s[right]] == need[s[right]]) {
valid++;
} // 注意:先加,再判断
// 判断左侧窗口是否要收缩
while (window needs shrink) {
// 左边界移出窗口,进行窗口内数据的一系列更新
if (window[s[left]] == need[s[left]]) {
valid--;
} // 注意:先判断,再减
window[s[left]]--;
left++; // 注意:左边届的收缩,要写在所有处理完成的最后
}
// 采集答案...
right++; // 注意:右边届的扩展,要写在所有处理完成的最后
}
}
目前大多数的滑动窗口都可以用这个模版
注意:可能会在各个地方去采集结果,一般会放在左窗口收缩后,因为此时窗口是满足题目要求的,但要「随机应变」
本题解法:
思路:指定移动步长+指定开始位置
滑动窗口模版题,一一把题目中的要求实现即可,目前做过的滑窗题都可以套模版
这道题和平常的滑窗题有些不同,但其实都一个套路
最重要的条件「长度相同」,暗示滑动窗口移动的步长 stride 为 words[0].size()
窗口的长度应为 words.size() * stride
当前窗口的长度计算公式为 right - left + stride
设定滑动窗口的起始位置,起始位置可选范围为 [0, stride-1]
时间复杂度:O(30 * n * 30) 遍历起始位置最大 30 次,滑动窗口遍历整个 s 为 n 次,滑窗内 substr 的截取和判断为最大 30 次
小知识
from collections import Counter
Counter()
主要功能:可以支持方便、快速的计数,
将元素数量统计,然后计数并返回一个字典,键为元素,值为元素个数。
class Solution:
def findSubstring(self, s, words):
# 遍历串长度
n =len(s)
# 步长 words 中单词长度一致,任意都可
stride=len(words[0])
# 限制条件 (总的目标串长度)
limit=len(words)*stride
# 将字串存入字典
need=collections.defaultdict(int) # 一种默认设定字典的方式 int 表示全为0
for x in words:
need[x]+=1
# 打印need结果为 defaultdict(<class 'int'>, {'foo': 1, 'bar': 1})
ans=[]
for start in range(0,stride):
left=start
right=start
# 记录满足条件数量
count=0
# 窗口初始化
window=collections.defaultdict(int)
# 窗口移动 与双指针类似
while right<n:
# 右边界进入
cur_right =s[right:right+stride]
# 判断子字符是否满足
if cur_right in need:
window[cur_right]+=1
# 判断是否已经找到满足条件的子字串,计数
if window[cur_right]==need[cur_right]:
count+=1
# 左边界收缩
if right-left+stride>limit:
cur_left=s[left:left+stride]
if cur_left in need:
if window[cur_left]==need[cur_left]:
count-=1
window[cur_left]-=1
# 窗口滑动
left+=stride
# 采集答案
if right - left + stride == limit and cnt == len(need):
ans.append(left)
right += stride
return ans
欢迎大家提问,给出建议。