滑动窗口算法实战:串联所有单词与最小覆盖子串
在处理字符串相关的区间问题时,滑动窗口往往能带来线性的时间复杂度。今天我们来拆解两个经典的滑动窗口变种题,看看如何灵活调整窗口策略来应对不同的约束条件。
15. 串联所有单词的子串
这道题要求找到字符串中所有由给定单词列表拼接而成的子串的起始位置。乍一看像是简单的查找,但难点在于单词长度固定且顺序不限,本质上是在寻找特定长度的'字母异位词'组合。
核心思路
如果我们把每个单词看作一个整体字符,问题就转化为了在字符串中寻找符合频次要求的连续序列。这里有几个关键点需要注意:
- 步长控制:左右指针不能每次移动一个字符,而应该以单词的长度为步长跳跃。
- 多轮遍历:由于起始位置可能落在单词内部的任意偏移量上(比如单词长度为 3,起始位置可能是 0, 1, 2),我们需要分别以这几种偏移量为起点执行滑动窗口,确保不遗漏任何情况。
- 频次校验:使用哈希表记录目标单词的频次,并在窗口移动时动态更新当前窗口的单词计数。
代码实现
class Solution {
public:
vector<int> findSubstring(string s, vector<string>& words) {
vector<int> ret;
unordered_map<string,int> hash1; // 保存 words 里面所有单词的频次
for(auto &w : words) hash1[w]++;
int len = words[0].size(), m = words.size();
// 执行 len 次,覆盖所有可能的起始偏移
for(int i = 0; i < len; i++) {
unordered_map<string,int> hash2; // 维护窗口内单词的频次
for(int left = i, right = i, count = 0; right + len <= s.size(); right += len) {
// 进窗口 + 维护 count
string in = s.substr(right, len);
hash2[in]++;
if(hash1.count(in) && hash2[in] <= hash1[in]) count++;
(right - left + > len * m) {
string out = s.(left, len);
(hash(out) && hash2[out] <= hash1[out]) count--;
hash2[out]--;
left += len;
}
(count == m) ret.(left);
}
}
ret;
}
};


