LeetCode 239. 滑动窗口最大值
题目描述
给你一个整数数组 nums 和一个大小为 k 的滑动窗口,窗口从数组的最左侧移动到最右侧,每次只向右移动一位。返回每个窗口中的最大值。
示例:
输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
解题思路
核心思想: 使用双端队列(deque)维护一个单调递减的下标序列。
- 队列中存储的是数组下标,且保证这些下标对应的元素值从队首到队尾严格递减。
- 队首元素对应的值就是当前滑动窗口的最大值。
算法步骤:
- 遍历数组,当前下标
i作为窗口的右边界。 - 维护递减性 :若当前元素
nums[i]大于等于队尾元素对应的值,则队尾元素不可能成为后续窗口的最大值,将其弹出。 - 将当前下标
i从队尾入队。 - 移除过期下标 :计算窗口左边界
left = i - k + 1,若队首下标小于left,说明该元素已滑出窗口,从队首弹出。 - 记录答案 :当
left >= 0时,当前窗口已完整,队首下标对应的值即为最大值,存入结果数组ans[left]。
代码实现(C++)
cpp
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
int n = nums.size();
deque<int> q; // 存储下标,保证值递减
vector<int> ans(n - k + 1); // 结果数组
for (int i = 0; i < n; ++i) {
// 1. 维护队列单调递减
while (!q.empty() && nums[q.back()] <= nums[i])
q.pop_back();
q.push_back(i);
// 2. 移除窗口外的元素
int left = i - k + 1;
if (q.front() < left)
q.pop_front();
// 3. 当窗口完整时记录最大值
if (left >= 0)
ans[left] = nums[q.front()];
}
return ans;
}
};
复杂度分析
- 时间复杂度: O(n)
每个元素最多入队一次、出队一次,总操作次数为 O(n)。 - 空间复杂度: O(n)
双端队列最多存储 k 个元素,结果数组需要 O(n - k + 1) 空间。
手动模拟示例
以 nums = [1,3,-1,-3,5,3,6,7], k = 3 为例:
| i | nums[i] | 队列状态(存下标) | 窗口范围 | 最大值 |
|---|---|---|---|---|
| 0 | 1 | [0] | - | - |
| 1 | 3 | 弹出 0,队列 [1] | - | - |
| 2 | -1 | 队列 [1,2] | [0~2] | 3 |
| 3 | -3 | 队列 [1,2,3] | [1~3] | 3 |
| 4 | 5 | 弹出 3,2,1,队列 [4] | [2~4] | 5 |
| 5 | 3 | 队列 [4,5] | [3~5] | 5 |
| 6 | 6 | 弹出 5,4,队列 [6] | [4~6] | 6 |
| 7 | 7 | 弹出 6,队列 [7] | [5~7] | 7 |
最终输出:[3,3,5,5,6,7] ✅
关键点总结
- 队列中存储的是下标,而不是元素值,方便判断元素是否滑出窗口。
- 双端队列从队尾 维护单调性(淘汰较小值),从队首淘汰过期值。
- 每个窗口的最大值就是队首元素对应的值,获取时间为 O(1)。