Python单调队列怎么用_滑动窗口最大值问题的最优解

3次阅读

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

Python 单调队列怎么用_滑动窗口最大值问题的最优解

单调队列到底该用 deque 还是 list

deque,别碰 list。原因很简单:单调队列核心操作是两端增删,deque 是 O(1),listpop(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,错了就整个结果偏移。

text=ZqhQzanResources