滑动窗口算法实战:串联所有单词子串与最小覆盖子串
在字符串处理问题中,滑动窗口是解决连续区间匹配的高效手段。今天我们来深入探讨两道经典题目:LeetCode 30 和 76。它们都涉及哈希表配合滑动窗口,但细节处理各有不同。
15. 串联所有单词的子串
题目描述
给定一个字符串 s 和一个字符串数组 words。words 中的所有单词长度相同。找出 s 中恰好包含 words 中所有单词的串联子串的起始索引。
解题思路
这道题的核心在于将'单词'视为'字符'。如果我们把每个单词看作一个整体单位,问题就转化为了寻找包含特定频次组合的异位词。
这里有几个关键点需要注意:
- 哈希表结构:需要统计
words中每个单词的出现频次,使用unordered_map<string, int>。 - 步长控制:左右指针移动的步长不再是 1,而是单词的长度
len。 - 多轮遍历:由于起始位置可能落在单词内部的任意偏移量上,我们需要对
0到len-1的每个偏移量分别执行一次滑动窗口逻辑。
C++ 实现
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; // 有效单词计数
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;
}
};




