滑动窗口不仅能解决基础的子串/子数组问题,还能处理字符统计、多模式匹配等复杂场景。本文继续介绍4道经典滑动窗口题目,覆盖"类型限制""异位词匹配""多单词串联""最小覆盖子串"等核心场景。
一、水果成篮
题目描述:
农场的果树用数组 fruits 表示(fruits[i] 是第 i 棵树的水果类型),你只有2个篮子(每个篮子只能装单一类型水果),需从某棵树开始连续采摘,返回能收集的水果最大数目。
示例:
- 输入:
fruits = [1,2,1],输出:3(采摘全部3棵树)
解题思路:
用滑动窗口维护"最多包含2种水果类型的连续区间":
- 用哈希表
windows统计窗口内水果类型的出现次数。 - 右指针
right遍历数组,将当前水果加入窗口并更新哈希表。 - 若窗口内类型数超过2,移动左指针缩小窗口(同时减少对应类型的计数,若计数为0则从哈希表中删除)。
- 每次调整后,更新最大水果数目。
完整代码:
cpp
class Solution {
public:
int totalFruit(vector<int>& fruits) {
unordered_map<int, int> windows;
int ret = 0;
for(int left = 0, right = 0; right < fruits.size(); right++) {
windows[fruits[right]]++;
while(windows.size() > 2) {
windows[fruits[left]]--;
if(windows[fruits[left]] == 0)
windows.erase(fruits[left]);
left++;
}
ret = max(ret, right - left + 1);
}
return ret;
}
};
复杂度分析:
- 时间复杂度: O ( n ) O(n) O(n),每个元素最多被左右指针各遍历一次。
- 空间复杂度: O ( 1 ) O(1) O(1),哈希表最多存储2种水果类型。
二、找到字符串中所有字母异位词
题目描述:
给定字符串 s 和 p,找出 s 中所有 p 的异位词(字母相同但顺序不同的子串)的起始索引。
示例:
- 输入:
s = "cbaebabacd", p = "abc",输出:[0,6](子串"cba"和"bac"是abc的异位词)
解题思路:
用固定长度的滑动窗口 (窗口长度= p 的长度),配合哈希数组统计字符频率:
- 用
hash1统计p中各字符的出现次数。 - 用
hash2统计窗口内s子串的字符频率,count记录窗口中"符合p字符频率要求"的字符数。 - 右指针扩展窗口,左指针在窗口长度超过
p时收缩,同时更新hash2和count。 - 当
count == p.size()时,当前窗口是异位词,记录左指针索引。
完整代码:
cpp
class Solution {
public:
vector<int> findAnagrams(string s, string p) {
vector<int> ret;
int hash1[26] = {0};
for(auto ch : p) hash1[ch - 'a']++;
int hash2[26] = {0};
int n = p.size();
for(int left = 0, right = 0, count = 0; right < s.size(); right++) {
char in = s[right];
if(++hash2[in - 'a'] <= hash1[in - 'a']) count++;
if(right - left + 1 > n) {
int out = s[left++];
if(hash2[out - 'a']-- <= hash1[out - 'a']) count--;
}
if(count == n) ret.push_back(left);
}
return ret;
}
};
复杂度分析:
- 时间复杂度: O ( n ) O(n) O(n),
n是s的长度,每个字符仅被遍历一次。 - 空间复杂度: O ( 1 ) O(1) O(1),哈希数组大小固定(26个小写字母)。
三、串联所有单词的子串
题目描述:
给定字符串 s 和单词数组 words(所有单词长度相同),返回 s 中包含 words 所有单词(任意顺序)的子串的起始索引。
示例:
- 输入:
s = "barfoothefoobarman", words = ["foo","bar"],输出:[0,9](子串"barfoo"和"foobar"是words的串联)
解题思路:
将"单词"视为"字符",用固定步长的滑动窗口(步长=单词长度):
- 用
hash1统计words中各单词的出现次数。 - 按单词长度
len分len组(避免遗漏起始位置),每组内用滑动窗口遍历:- 窗口长度=
words.size() * len,用hash2统计窗口内单词的出现次数。 - 右指针每次移动
len步(取一个单词),左指针在窗口超限时同步移动len步。 - 用
count记录窗口中"符合words单词频率要求"的单词数,当count == words.size()时记录左指针索引。
- 窗口长度=
完整代码:
cpp
class Solution {
public:
vector<int> findSubstring(string s, vector<string>& words) {
vector<int> ret;
unordered_map<string, int> hash1;
for(auto s : words) hash1[s]++;
int len = words[0].size(), m = words.size();
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) {
string in = s.substr(right, len);
hash2[in]++;
if(hash1.count(in) && hash2[in] <= hash1[in]) count++;
if(right - left + 1 > len * m) {
string out = s.substr(left, len);
if(hash1.count(out) && hash2[out] <= hash1[out]) count--;
hash2[out]--;
left += len;
}
if(count == m) ret.push_back(left);
}
}
return ret;
}
};
复杂度分析:
- 时间复杂度: O ( n × l e n ) O(n \times len) O(n×len),
n是s的长度,len是单词长度(分组遍历的总复杂度为 O ( n ) O(n) O(n))。 - 空间复杂度: O ( m ) O(m) O(m),
m是words的长度(哈希表存储单词频率)。
四、最小覆盖子串
题目描述:
给定字符串 s 和 t,返回 s 中包含 t 所有字符(包括重复)的最短子串;若不存在则返回空串。
示例:
- 输入:
s = "ADOBECODEBANC", t = "ABC",输出:"BANC"
解题思路:
用滑动窗口维护"包含 t 所有字符的区间",动态收缩窗口找最短长度:
- 用
hash1统计t中各字符的出现次数,kinds记录t中不同字符的数量。 - 用
hash2统计窗口内字符的出现次数,count记录窗口中"满足t字符频率要求"的字符种类数。 - 右指针扩展窗口,当
count == kinds时,尝试移动左指针收缩窗口(同时更新最短子串的长度和起始位置)。 - 遍历结束后,根据记录的起始位置和长度返回结果。
完整代码:
cpp
class Solution {
public:
string minWindow(string s, string t) {
int hash1[128] = {0};
int kinds = 0;
for(auto ch : t)
if(hash1[ch]++ == 0) kinds++;
int hash2[128] = {0};
int len = INT_MAX, begin = -1;
for(int left = 0, right = 0, count = 0; right < s.size(); right++) {
char in = s[right];
hash2[in]++;
if(hash2[in] == hash1[in]) count++;
while(count == kinds) {
if(right - left + 1 < len) {
len = right - left + 1;
begin = left;
}
char out = s[left++];
if(hash2[out] == hash1[out]) count--;
hash2[out]--;
}
}
if(begin == -1) return "";
return s.substr(begin, len);
}
};
复杂度分析:
- 时间复杂度: O ( n ) O(n) O(n),
n是s的长度,每个字符仅被遍历一次。 - 空间复杂度: O ( 1 ) O(1) O(1),哈希数组大小固定(128个ASCII字符)。