作为一名在算法竞赛和面试辅导领域深耕多年的从业者,我经常遇到学员对滑动窗口和单调队列这两个高频考点感到困惑。今天我将通过LeetCode三道经典题目(560. 和为K的子数组、239. 滑动窗口最大值、76. 最小覆盖子串),带大家彻底掌握这些核心算法的底层原理和实战技巧。
560题要求统计数组中所有和为K的连续子数组数量。最直观的暴力解法是双重循环枚举所有子数组,时间复杂度O(n²)。但当数组长度达到2×10⁴时,这种解法必然超时。
关键观察:子数组的和可以转化为前缀和的差值。设前缀和数组s,则s[j]-s[i]正好表示子数组nums[i+1...j]的和。这个转化将问题从"子数组和"转换为"前缀和差值"。
cpp复制class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
unordered_map<int, int> prefixSumCount;
prefixSumCount[0] = 1; // 处理从0开始的子数组
int sum = 0, count = 0;
for (int num : nums) {
sum += num;
if (prefixSumCount.find(sum - k) != prefixSumCount.end()) {
count += prefixSumCount[sum - k];
}
prefixSumCount[sum]++;
}
return count;
}
};
prefixSumCount[0]=1处理sum直接等于k的情况prefixSumCount[0]导致漏解239题需要在O(n)时间内解决滑动窗口最大值问题。单调队列通过维护一个递减队列,保证队首始终是当前窗口最大值。
cpp复制class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
deque<int> dq;
vector<int> res;
for (int i = 0; i < nums.size(); ++i) {
// 移除超出窗口范围的元素
if (!dq.empty() && dq.front() == i - k) {
dq.pop_front();
}
// 维护单调递减性
while (!dq.empty() && nums[dq.back()] < nums[i]) {
dq.pop_back();
}
dq.push_back(i);
// 记录结果
if (i >= k - 1) {
res.push_back(nums[dq.front()]);
}
}
return res;
}
};
| 方法 | 时间复杂度 | 空间复杂度 | 适用场景 |
|---|---|---|---|
| 暴力法 | O(nk) | O(1) | k很小时 |
| 平衡二叉搜索树 | O(nlogk) | O(k) | 数据流场景 |
| 单调队列 | O(n) | O(k) | 最优解 |
76题要求在字符串s中找到包含t所有字符的最短子串。这类"满足条件的最小区间"问题通常采用滑动窗口模板:
cpp复制string minWindow(string s, string t) {
unordered_map<char, int> need, window;
for (char c : t) need[c]++;
int left = 0, right = 0;
int valid = 0;
int start = 0, len = INT_MAX;
while (right < s.size()) {
// 右扩窗口
char c = s[right++];
if (need.count(c)) {
window[c]++;
if (window[c] == need[c]) valid++;
}
// 左缩窗口
while (valid == need.size()) {
if (right - left < len) {
start = left;
len = right - left;
}
char d = s[left++];
if (need.count(d)) {
if (window[d] == need[d]) valid--;
window[d]--;
}
}
}
return len == INT_MAX ? "" : s.substr(start, len);
}
面对子数组/子字符串问题时,可参考以下决策流程:
code复制是否要求连续? → 否 → 考虑回溯或DP
↓是
是否需要精确匹配? → 是 → 滑动窗口(如最小覆盖子串)
↓否
是否涉及区间和? → 是 → 前缀和+哈希(如和为K的子数组)
↓否
是否需要区间最值? → 是 → 单调队列(如滑动窗口最大值)
↓否
考虑普通滑动窗口或双指针
| 问题 | 边界案例 | 检查要点 |
|---|---|---|
| 和为K的子数组 | nums=[0,0,0], k=0 | 多个零组合的计数 |
| 滑动窗口最大值 | nums=[1,3,1,2,0,5], k=3 | 窗口收缩时的队列更新 |
| 最小覆盖子串 | s="a", t="aa" | 字符频率不足时的返回 |
单调队列的每个元素最多入队出队各一次,虽然单次操作可能是O(n),但总体是O(n)
对于固定字符集的问题(如仅字母),可用大小为128的数组替代哈希表
在实际刷题过程中,我建议将这三道题作为滑动窗口和单调队列的基准测试题。每道题至少手写3遍,直到能无bug一次通过。对于工程面试,滑动窗口的应用频率尤其高,需要重点掌握其变种问题的解法。