滑动窗口最大值不用std::deque难以高效实现,因其支持头尾o(1)插删,可构建单调递减队列,将均摊时间从o(nk)降至o(n),队列存下标以快速淘汰过期无用候选。

滑动窗口最大值为什么不用 std::deque 就很难高效实现
因为暴力法(每次窗口内遍历找最大)是 O(nk),而用 std::deque 维护单调递减序列,能把均摊时间压到 O(n)。关键不在于“用了 deque”,而在于它支持头尾 O(1) 插删——这是实现单调队列的物理基础。
注意:std::deque 不是拿来直接存窗口最大值的容器,而是作为**下标/值的双端缓存结构**,用来快速淘汰过期、无用的候选值。
如何用 std::deque 实现单调递减队列
核心逻辑:队列里存的是数组下标(推荐),或原始值(需额外处理重复和边界)。保持队首始终是当前窗口最大值的下标,且队列内对应值严格单调递减。
- 每步先弹出队首过期下标:
while (!dq.empty() && dq.front() - 从队尾开始弹出所有 ≤ 当前值的下标(破坏单调性):
while (!dq.empty() && nums[dq.back()] - 把当前下标
i推入队尾:dq.push_back(i); - 窗口成型后(
i >= k - 1),队首就是最大值下标:result.push_back(nums[dq.front()]);
注意:比较时用 nums[dq.back()] ,不是 <code>dq.back() ——容易错写成下标和值混比。
立即学习“C++免费学习笔记(深入)”;
std::deque 和手写数组模拟单调队列的区别在哪
本质没区别,但 std::deque 省去了手动管理环形数组边界、扩容、越界检查的麻烦。不过要注意:
-
std::deque::front()/back()是 O(1),但operator[]随机访问是分段常数(非真 O(1)),所以别用下标遍历 deque;只用头尾接口 - 内存局部性不如数组,高频小窗口场景下,手写固定大小数组可能更快(但代码更脆)
- 不要用
std::list替代——它虽也支持头尾操作,但迭代器失效规则不同,且 cache 友好性更差
实际项目中,除非 profiling 明确卡在 deque 的内存跳转上,否则优先用 std::deque——它语义清晰、不易出错。
边界与易错点:空窗口、重复值、k=1 或 k > n
这些情况不会让算法崩,但结果容易错:
- k == 0:提前返回空 vector,避免后续循环越界
- k > nums.size():按题意通常不出现,但若出现,应只保留一个窗口(即整个数组),别让
i - k变负数导致 unsigned 下溢(如果 deque 存 size_t 下标) - 重复值:比如
[3,3,3,3],单调队列会把前面的下标全 pop_back 掉,只剩最后一个——这正确,因为新下标更晚过期 - 初始化阶段(i
最隐蔽的坑是:用 int 下标却把 size_t 的 nums.size() 直接参与 i - k 计算,导致极大正数下标——务必统一有符号类型。











