最近审阅:2026-10-09 · 先读:栈、堆与链表
[4,1,3,5,2,5],每次看连续 3 个数,最大值依次是 [4,5,5,5]。每移动一次都重新求 max,当然能做,但长度为 $n$、窗口为 $k$ 时要 $O(nk)$。
只记一个最大值又不够:4 离开窗口后,要知道谁接班。单调队列(monotonic queue)保留的,就是仍有可能接班的下标。
什么情况下,一个数可以永久删掉
现在已有一个较早的 1,后来来了 3。只要未来窗口里还包含这个 1,就一定也包含后来的 3;3 更大,还更晚过期。这个 1 永远不会再成为最大值,可以删掉。
这依赖窗口右端只向前走、宽度固定。不是说 1 从数据里消失了,而是它不再可能影响我们要的答案。如果你要窗口总和、第二大值或所有并列最大值的位置,不能随便扔。
队列存下标而不是数值,才能判断有没有过期。我们维持两条性质:下标从左到右增加,对应的值从左到右严格减小。
每一步有两种不同的删除
- 从队首移除离开窗口的下标。
- 从队尾移除不大于新值的下标。
- 加入新下标;窗口凑齐以后,队首就是最大值。
| 新下标和值 | 处理后的队列:下标(值) | 本窗口最大值 |
|---|---|---|
| 0: 4 | 0(4) | 窗口未满 |
| 1: 1 | 0(4), 1(1) | 窗口未满 |
| 2: 3 | 0(4), 2(3) | 4 |
| 3: 5 | 3(5) | 5 |
| 4: 2 | 3(5), 4(2) | 5 |
| 5: 5 | 5(5) | 5 |
最后一个 5 到来时,旧 5 也被删掉。求最大值时留更新的那个就够了;若要求最早出现的位置,就改用严格小于来删队尾,并重新核对并列规则。
Python 实现
def window_maxima(values, width):
if type(width) is not int or not 1 <= width <= len(values):
raise ValueError("width must be an integer between 1 and input length")
candidates = deque()
maxima = []
for index, value in enumerate(values):
while candidates and candidates[0] <= index - width:
candidates.popleft()
while candidates and values[candidates[-1]] <= value:
candidates.pop()
candidates.append(index)
if index >= width - 1:
maxima.append(values[candidates[0]])
return maxima
先 from collections import deque。这里拒绝空输入和不在 1…len(values) 之间的窗口宽度,比较的值应当可排序且不含 NaN。不要换成 list.pop(0):列表头删需要移动后面的元素;deque 适合两端操作。Python collections 文档
index - width 是刚刚过期的下标,当前窗口左端是 index - width + 1。这是最容易错一位的地方。
两个 while 会不会变成平方时间
每个下标进队一次,最多出队一次:要么从头过期,要么从尾被更好的候选(candidate)淘汰。同一个下标不会被删两次,所以总队列操作是 $O(n)$,辅助空间(auxiliary space) $O(k)$,输出另占 $O(n-k+1)$。
数组严格递减时,队列会保留整个窗口;严格递增时,常常只剩最新一个。不能拿后一种输入推断空间永远是 $O(1)$。
和堆、单调栈有什么不同
| 方法 | 保留什么 | 代价或限制 |
|---|---|---|
| 逐窗 max | 整个窗口 | 最直观,$O(nk)$ |
| 单调队列 | 尚未被支配的窗口候选 | $O(n)$,依赖窗口向前移动 |
| 懒删除堆 | 值与下标 | 只弹过期堆顶,埋在堆里的旧项可能一直留下,最坏 $O(n)$ 空间 |
| 单调栈 | 等待未来答案的元素 | 适合“右边第一个更大值”;没有自动处理窗口到期 |
懒删除堆若不定期重建或支持索引删除,不能顺手写成 $O(k)$ 空间。需要任意位置删除、分位数等查询时,再考虑更强的数据结构。
换个条件试试
把输入改成 [5,5,5]、k=2,答案仍是 [5,5]。再试 k=1、k=n、全负数和递减输入。你应该能说清每个下标为什么被删,而不只是背两段 while。
代码与小数组穷举测试在 deeper_patterns.py 和 test_deeper_algorithm_patterns.py。这是一种窗口方法的教学演示,不代表需要把复习重心转向 Hard 题。
本页术语 3
- 单调队列 monotonic queue
- 保留按值单调排列的候选;配合过期规则维护窗口中的极值
- 候选 candidate
- 召回阶段捞出来、等待精排挑选的内容
- 辅助空间 auxiliary space
- 除输入和输出外,算法执行时额外使用的空间,包括调用栈