滑动窗口实战:两道经典字符串匹配题
在算法面试中,滑动窗口是处理连续子串或子数组问题的利器。今天我们来深入探讨两个高频考点:串联所有单词的子串和最小覆盖子串。这两道题虽然场景不同,但核心思想都是通过动态调整窗口边界来寻找最优解。
1. 串联所有单词的子串
这道题要求找到包含 words 列表中所有单词(每个单词长度相同)的起始位置。乍一看像是复杂的排列组合,但如果我们把每个单词看作一个'超级字符',问题就简化成了寻找异位词的问题。
核心思路
关键在于理解窗口的移动方式:
- 既然每个单词长度固定为
len,那么窗口的左右指针每次移动的步长也应该是len。 - 我们需要遍历整个字符串,但因为是从不同偏移量开始的,所以外层循环需要执行
len次,分别以0到len-1作为起始偏移。 - 内部维护一个哈希表统计当前窗口内单词的出现次数,并与目标单词表的频次进行比对。
代码实现
class Solution {
public:
vector<int> findSubstring(string s, vector<string>& words) {
vector<int> ret;
unordered_map<string, int> hash1; // 统计目标单词频次
for(auto& e : words) {
hash1[e]++;
}
int len = words[0].size(); // 单词长度
int m = words.size(); // 单词数量
int n = s.size(); // 字符串总长度
// 从 0 到 len-1 开始遍历,覆盖所有可能的起始偏移
for(int i = 0; i < len; i++) {
unordered_map<string, int> hash2; // 当前窗口单词频次
int count = 0; // 有效单词计数
int left = i;
( right = i; right + len <= n; right += len) {
string str1 = s.(right, len);
hash2[str1]++;
(hash2[str1] <= hash1[str1]) {
count++;
}
(right - left + > len * m) {
string str2 = s.(left, len);
(hash2[str2] <= hash1[str2]) {
count--;
}
hash2[str2]--;
left += len;
}
(count == m) {
ret.(left);
}
}
}
ret;
}
};




