第3章|栈与队列
Unit 10 · 单调队列与滑动窗口极值
窗口每移动一步,
不必重新寻找最大值。
真正有价值的不是窗口中的全部元素,而是“仍在窗口内、并且未来仍可能成为最大值”的候选索引。双端队列用队首处理过期,用队尾处理支配关系。
题目要求
给定整数数组 nums 和窗口大小 k。窗口从数组最左端开始,每次向右移动一个位置。返回每个窗口中的最大值。
输入、输出与复杂度目标
- 输入:整数数组
nums与合法窗口大小k; - 输出:长度为
len(nums)-k+1的窗口最大值数组; - 目标:时间复杂度
O(n),辅助空间O(k)。
示例
输出:[3,3,5,5,6,7]
输出:[1]
数据约束
1 ≤ nums.length ≤ 10⁵-10⁴ ≤ nums[i] ≤ 10⁴1 ≤ k ≤ nums.length
长度固定为 k 的连续区间。右端加入新元素时,左端可能有旧索引过期。
队列保存索引而不是只保存值,才能判断候选是否已经离开当前窗口。
更晚且不小于旧值的新元素,会比旧元素存活更久,因此旧元素不再可能成为未来最大值。
队列索引递增、对应值严格递减;队首始终是当前窗口最大值索引。
#84 用当前更矮柱一次性封闭历史柱子的右边界;#239 则要求候选在多个重叠窗口中持续存活。共同点是删除已失去未来资格的历史状态,新增点是队首还要承担时间过期检查,因此容器从单端栈升级为双端队列。
候选双端队列 索引递增 · 值递减
窗口答案 尚未形成完整窗口
索引小于窗口左端 left 的候选已经过期,必须执行 popleft()。
值不大于当前值的旧候选已被支配,执行 pop() 后再加入当前索引。
当前窗口 [1, 3, -1] 的最大值是什么?
分层提示 1|窗口边界 left = right - k + 1 分层提示 2|两个删除方向 队首只处理“索引是否过期”;队尾只处理“值是否被更晚元素支配”。 分层提示 3|不变量 candidates 中索引递增,对应值严格递减,因此当前窗口最大值位于队首。 分层提示 4|复杂度 每个索引只会入队一次,并从队首或队尾离队至多一次,总时间 O(n)。 请独立补全两个 while 条件以及队首输出表达式。
迁移题:LeetCode #1438「绝对差不超过限制的最长连续子数组」
不要先看代码。先确认题目输出、窗口合法性和窗口为何必须动态收缩,再把 #239 的一个极值队列扩展为最大值与最小值两个队列。
官方题意与函数签名
给定整数数组 nums 和整数 limit,返回最长非空连续子数组的长度,使该子数组中任意两个元素的绝对差都不超过 limit。
对任意窗口,任意两数绝对差的最大值等于 max(window) - min(window),所以合法条件可压缩为一条极值约束。
官方样例与约束
1 ≤ nums.length ≤ 10⁵1 ≤ nums[i] ≤ 10⁹0 ≤ limit ≤ 10⁹- 返回长度,不返回子数组本身。
读题门槛:先确认输出和合法性
kleft = right-k+1left += 1槽位 1|状态表示
窗口合法性依赖哪两个快速查询?
槽位 2|收缩触发
什么时候必须移动左端?
槽位 3|同步过期
left 离开窗口时如何维护两个队列?
槽位 4|目标更新
窗口恢复合法后记录什么?
完成四个语义槽位后,这里将拼装 #1438 的核心代码骨架。
可变窗口与双单调队列轨迹
当前窗口契约
while,因为 left 可能需要移动多次才能恢复 max-min ≤ limit。两个队列只提供极值;真正决定保留哪些元素的是连续窗口边界。可选挑战:独立重写可提交版本
提示 1:max_deque 队尾删除不大于当前值的索引;min_deque 队尾删除不小于当前值的索引。 提示 2:当两个队首之差超过 limit,检查哪个队首等于 left,并让 left 向右移动。 提示 3:窗口恢复合法后,以 right-left+1 更新最长长度。 提示 4:每个索引在每个队列中至多入队、出队一次,时间复杂度 O(n)。
Unit 10 Skill:用双端队列维护“未来仍有资格”的极值候选
固定滑动窗口中,不保存全部元素,而只保存仍在窗口内、且没有被更晚更大元素支配的索引。队首负责过期,队尾负责单调性,队首值直接输出为窗口最大值。