该用 deque 而非 list,因单调队列需两端 o(1) 增删,list 的 pop(0) 为 o(n),大数据易超时;应存下标、维护单调递减、检查下标是否滑出窗口。

单调队列到底该用 deque 还是 list
用 deque,别碰 list。原因很简单:单调队列核心操作是两端增删,deque 是 O(1),list 的 pop(0) 或 insert(0, x) 是 O(n) —— 滑动窗口一多,直接拖垮性能。
常见错误现象:list 实现看似能过小样例,但遇到 10⁵ 长度数组 + 窗口大小 10⁴ 就超时;错误日志里看不到报错,只有 Time Limit Exceeded。
- 初始化写
from collections import deque,不是import collections - 队列存的是下标(不是值),方便判断是否滑出窗口
- 入队前从队尾弹出所有 ≤ 当前值的元素(维持单调递减)
- 出队前检查队首下标是否 i - k + 1(窗口左边界),越界就
popleft()
为什么不能直接存数值而要存下标
因为窗口会滑动,只存数值无法判断“这个最大值还在不在当前窗口里”。下标能和当前索引 i 做差,立刻算出是否过期。
使用场景:比如 nums = [1,3,-1,-3,5,3,6,7],k = 3,当遍历到 i = 4(值为 5)时,队列里可能还有下标 1(值为 3)——它对应的位置是 nums[1],而窗口是 [2,3,4],下标 1 已失效。
立即学习“Python免费学习笔记(深入)”;
- 存下标后,判断过期只需
if dq[0] ,干净利落 - 如果硬存数值,就得额外维护一个“位置映射”,或者每次扫描队列查范围,O(k) 开销白丢
- 输出结果时,用
nums[dq[0]]取真实值,不是dq[0]
边界条件不处理好,第一个窗口就崩
窗口从下标 0 开始,但前 k-1 个元素不能立即输出结果 —— 还没凑满窗口。很多人在这里漏判,导致结果数组多出 k-1 个错误值或索引错误。
典型错误现象:IndexError: list index out of range 出现在 res[i - k + 1],或者返回结果比预期长。
- 初始化空列表
res = [],只在i >= k - 1时才append - 不要用
res[i - k + 1] = ...赋值,避免预分配引发越界 - 第一次进循环时,队列为空,
while dq and nums[dq[-1]] 不会报错,但要注意 <code>dq是空列表时dq[-1]会崩 —— 所以判断必须写成while dq and nums[dq[-1]] ,顺序不能反
Python 里 pop() 和 popleft() 别混用
pop() 从右端出,popleft() 从左端出 —— 单调队列里,维护单调性靠 pop()(删尾),维护窗口有效性靠 popleft()(删头)。写反了就全乱套。
容易踩的坑:看到“删旧值”就条件反射写 pop(0),但那是 list 写法,在 deque 里该用 popleft();更隐蔽的是把 pop() 误写成 pop(0),虽然 deque.pop(0) 合法,但它是 O(n),彻底废掉双端队列优势。
- 删尾统一用
dq.pop() - 删头统一用
dq.popleft() - 永远不要对
deque调用pop(0)或insert(0, x) - 调试时可以打印
len(dq)和dq[0], dq[-1],确认头尾更新符合预期
实际写的时候,最麻烦的不是逻辑,是下标算错两处:一是窗口左边界 i - k + 1 容易写成 i - k,二是结果索引 i - k + 1 在循环里起始位置没对齐。这两个数差 1,错了就整个结果偏移。










