算法题解
LeetCode Hot 100 | 11. 滑动窗口最大值
通过自定义单调递减队列,在 O(N) 时间内求解每个滑动窗口的最大值。
LeetCodeHot100滑动窗口C++面试算法
核心思路:单调队列
为什么不能用普通队列?
普通队列只能先进先出,想要在 时间内找到队列中的最大值是不可能的(通常需要 遍历)。
单调队列的定义:
我们需要维护一个队列,使得队列中的元素 单调递减(队头始终是当前窗口的最大值)。
为了实现这一点,我们需要自定义 push 和 pop 的规则:
-
入队 (
push) —— 优胜劣汰:- 当我们尝试把一个新元素
val放进队尾时,如果发现队尾原本的元素比val小,说明那些元素既比val小(能力差),又比val进来的早(寿命短),它们在val存在的窗口期内永远不可能成为最大值。 - 操作:直接把这些"累赘"从队尾踢出 (
pop_back),直到队尾元素大于val或队列为空,再把val放进去。
- 当我们尝试把一个新元素
-
出队 (
pop) —— 身份核验:- 当滑动窗口向右移动,索引 的元素需要移出窗口。
- 关键判断:我们只需要检查 队头元素 是否等于这个要移除的元素。
- 如果相等:说明这个最大值正是我们要移除的那个老将,必须
pop_front。 - 如果不等:说明这个要移除的元素早在之前
push更大元素时,就已经被"优胜劣汰"给踢出去了。它不在队列里,所以不需要做任何操作。
- 如果相等:说明这个最大值正是我们要移除的那个老将,必须
代码实现
class Solution {
private:
// 自定义单调队列类
class MonotonicQueue {
public:
deque<int> dq; // 双端队列:支持头尾操作
// 1. 入队:维护单调递减性质
// 核心口诀:比我小还比我老的,统统卷铺盖走人
void push(int val) {
while (!dq.empty() && val > dq.back()) {
dq.pop_back();
}
dq.push_back(val);
}
// 2. 出队:只有当要移除的元素恰好是队头最大值时,才真正弹出
// 否则,说明它早在 push 阶段就被更大的元素挤走了
void pop(int val) {
if (!dq.empty() && val == dq.front()) {
dq.pop_front();
}
}
// 3. 获取最大值:永远是队头
int max() {
return dq.front();
}
};
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
MonotonicQueue window;
vector<int> result;
// Step 1: 先初始化第一个窗口
for (int i = 0; i < k; i++) {
window.push(nums[i]);
}
result.push_back(window.max()); // 记录第一个窗口的最大值
// Step 2: 窗口开始滑动
for (int i = k; i < nums.size(); i++) {
// A. 移除旧元素 (nums[i-k])
window.pop(nums[i - k]);
// B. 加入新元素 (nums[i])
window.push(nums[i]);
// C. 记录当前窗口最大值
result.push_back(window.max());
}
return result;
}
};
复杂度分析
这是一个经典的 均摊复杂度 (Amortized Analysis) 案例。
- 时间复杂度:
- 乍一看
push中有一个while循环,似乎是 ? - 其实不然:数组中的每一个元素,最多只会被
push进队列 一次,也最多会被pop出队列 一次。 - 没有任何元素会被反复进出,所以总的操作次数与 成线性关系。
- 乍一看
- 空间复杂度:
- 队列中最多存储 个元素(当数组本身是单调递减时,队列会存满 个)。
关键点备忘
- 队列存什么?
- 本解法存的是 数值 (Value)。
- 另一种流派:队列存 下标 (Index)。存下标的好处是判断
pop时不需要比对数值,而是直接看index <= i-k即可。但存数值逻辑更直观,更符合"单调队列"的数据结构定义。
deque的必要性:- 我们需要
push_back(正常入队),pop_back(维护单调性),pop_front(滑动窗口移出),front(获取最大值)。只有双端队列deque能同时满足这四个操作。
- 我们需要
- 场景拓展:
- 单调队列不仅仅用于滑动窗口最大值,它在 动态规划 (DP) 优化 中也非常重要(例如:优化形如 且 有范围限制的转移方程)。