当前位置: 首页 > news >正文

单调栈的“近亲”:用 O(n) 的「单调队列」征服「滑动窗口最大值」

哈喽各位,我是前端小L。

我们的单调栈之旅,已经让我们能够高效地处理“凹槽”(接雨水)和“边界”(柱状图)问题。这些问题的共同点是,我们只关心“入栈”和“出栈”(push_back, pop_back)。

但如果,我们的问题是一个固定大小的“窗口”,在数据流上滑动呢?当窗口向右移动一格时,一个新元素从右侧进入,一个旧元素从左侧离开。

  • “右侧进入,右侧弹出”——单调栈可以胜任。

  • “左侧离开”——单调栈做不到!

这,就是我们需要“双端队列 (Deque)”的原因。而“单调队列”,就是这个问题的终极答案。

力扣 239. 滑动窗口最大值

https://leetcode.cn/problems/sliding-window-maximum/

题目分析:

  • 输入:一个数组 nums 和一个窗口大小 k

  • 过程:一个大小为 k 的窗口,从数组的最左侧,一次向右移动一格,直到最右侧。

  • 目标:返回一个数组,包含窗口每一步移动时的最大值

例子: nums = [1, 3, -1, -3, 5, 3, 6, 7], k = 3

  • [1, 3, -1] -3, 5, 3, 6, 7 -> Max = 3

  • 1, [3, -1, -3] 5, 3, 6, 7 -> Max = 3

  • 1, 3, [-1, -3, 5] 3, 6, 7 -> Max = 5

  • 1, 3, -1, [-3, 5, 3] 6, 7 -> Max = 5

  • 1, 3, -1, -3, [5, 3, 6] 7 -> Max = 6

  • 1, 3, -1, -3, 5, [3, 6, 7] -> Max = 7

  • 答案: [3, 3, 5, 5, 6, 7]

思路的演进

  1. 朴素暴力 (O(n*k)): 遍历 n-k+1 个窗口,对每个窗口都花 O(k) 时间找到最大值。总时间 O(n*k),太慢。

  2. 大顶堆 / 优先队列 (O(n log k)): 维护一个大小为 k 的大顶堆。窗口滑动时,add (O(log k)),remove (O(k) 或 O(log k) 如果用 multiset)。总时间 O(n log k) 或 O(n*k)。还是不够好。

“Aha!”时刻:单调队列的“四步舞” (O(n))

我们需要一个神奇的数据结构,它能 O(1) 地提供当前窗口的最大值,并且在窗口滑动时,能高效地 O(1)(均摊)添加和删除元素。

这个结构就是单调递减队列(存储索引): deque<int> dq; 这个队列有两条铁律:

  1. 队列中的索引对应的,必须严格单调递减

  2. 队列中只存储当前窗口内的索引。

算法流程(“四步舞”): 我们遍历 nums 数组,索引为 i

第1步:(维护窗口) 移除队首的“过期”索引

  • if (!dq.empty() && dq.front() == i - k)

  • dq.pop_front();

  • 解释i-k 是刚滑出窗口的那个元素的索引。如果它恰好是队首(即它是之前窗口的最大值),我们必须将它从队首移除。

第2步:(维护单调) 移除队尾的“无用”索引

  • while (!dq.empty() && nums[i] >= nums[dq.back()])

  • dq.pop_back();

  • 解释:当前元素 nums[i] 比队尾的元素 nums[dq.back()] 要大(或相等)。而 nums[i] 更“新”(在窗口里待得更久)。这意味着,队尾那个“又老又小”的元素,永远不可能成为未来任何窗口的最大值了(因为有 nums[i] 挡着)。所以,将它从队尾无情地弹出。

第3步:(添加元素) 将当前索引加入队尾

  • dq.push_back(i);

  • 解释:经过第2步的“清理”,nums[i] 现在可以安全地加入队尾,并维持队列的单调递减性。

第4步:(报告答案) 记录队首的最大值

  • if (i >= k - 1)

  • result.push_back(nums[dq.front()]);

  • 解释i >= k-1 意味着窗口已经“满”了。根据我们的单调递减规则,队首 dq.front() 对应的 nums[dq.front()],永远是当前窗口内的最大值

复杂度深度分析

  • 时间复杂度 O(n)

    • 这是一个经典的摊销分析 (Amortized Analysis)

    • 表面上看,for 循环内部有一个 while 循环,似乎是 O(n²)。

    • 但是,我们来分析每个元素的“一生”:nums[i]索引 i,最多只入队一次push_back)。

    • 它也最多只出队一次(要么 pop_front,要么 pop_back)。

    • n 次的 for 循环中,push_back 的总操作是 n 次。pop_frontpop_back 的总操作加起来也绝不会超过 n

    • 所有的 pushpop 操作总共是 O(n) 级别。

    • 将这些 O(n) 的操作成本,平摊nfor 循环上,平均每次循环的时间复杂度就是 O(1)。

    • 总时间复杂度 O(n)

  • 空间复杂度 O(k)

    • 在最坏的情况下,例如一个严格递减的数组 [5, 4, 3, 2, 1]while 循环(第2步)永远不会执行。

    • 此时,窗口内的所有 k 个元素的索引都会被压入双端队列中。

    • 因此,dq 的大小最多为 k

代码实现

#include <vector>
#include <deque>using namespace std;class Solution {
public:vector<int> maxSlidingWindow(vector<int>& nums, int k) {vector<int> result;// deque 中存储的是索引,其对应的值是单调递减的deque<int> dq; for (int i = 0; i < nums.size(); ++i) {// 1. (维护窗口) 移除队首的“过期”索引if (!dq.empty() && dq.front() == i - k) {dq.pop_front();}// 2. (维护单调) 移除队尾的“无用”索引while (!dq.empty() && nums[i] >= nums[dq.back()]) {dq.pop_back();}// 3. (添加元素) 将当前索引加入队尾dq.push_back(i);// 4. (报告答案) 窗口已满if (i >= k - 1) {result.push_back(nums[dq.front()]);}}return result;}
};

总结:从“栈”到“队列”的进化

今天,我们成功地将单调栈(LIFO)进化为了单调队列(Deque, FIFO + LIFO)

  • 单调栈:适用于解决“下一个/上一个 更大/更小”问题,它的“窗口”是不固定的(从ileft_bound/right_bound)。

  • 单调队列:适用于解决“固定大小的滑动窗口”的最值问题,它必须同时处理“从尾部加入”和“从头部移除”。

掌握了单调队列,你就拥有了在 O(n) 时间内处理所有“滑动窗口最值”问题的终极武器!

下期见!

http://www.dtcms.com/a/568455.html

相关文章:

  • Buildroot构建Linux系统根文件系统
  • 在自动驾驶数据闭环中的特征工程应用(上)
  • 【具身智能】Spatial Forcing 论文笔记 如何隐式地为 VLA 注入 3D 空间感知能力
  • 多模态技术深度探索:融合视觉与语言的AI新范式
  • 自动化单mysql多实例库的全量迁移脚本-v2版本
  • [CARLA系列--04]如何在Carla中去调用传感器模型--相机篇
  • 【ASP.NET MVC 进阶】DataAnnotations 特性验证全解析:从基础到避坑,让数据校验像 “安检“ 一样靠谱
  • 做ppt兼职的网站有哪些北京中燕建设公司网站
  • webgl 顶点、片元着色器传参,绘制彩色三角形
  • 实验室安全教育与管理平台学习记录(八)特种设备安全
  • 浙江网站制作国外翻墙设计网站
  • 《神经网络与深度学习》学习笔记一
  • 超越蓝牙与Wi-Fi,UWB技术如何解锁手机下一波创新浪潮?
  • 【VPX650G】基于 VPX 系统架构的 JFM9VU13P FPGA+JFMQL100TAI 超宽带信号处理平台
  • 软考 系统架构设计师系列知识点之杂项集萃(190)
  • Linux信号(下):信号保存和信号处理
  • 仅需一部智能手机,在NVIDIA Isaac Sim中重建真实场景:终极实战指南
  • Spring设计模式刨根问底
  • 河南郑州做网站汉狮网站赚钱的方式
  • 不是万维网的网站如何注册公司抖音号
  • AI 赋能科研实践:从选题到发表的技术重构之路
  • 技术的秩序:IT资产与配置管理的现代重构
  • 告别布线噩梦:8公里LoRa边缘网关如何重构工业物联边界
  • Python 图像处理利器:Pillow 深度详解与实战应用
  • 【数据结构】:二叉树——顺序结构,链式结构的实现及相关操作
  • RS485转以太网串口服务器-串口设备联网的理想选择
  • 电动化筑基:智能社会的能源革命与产业重构
  • 【深度学习新浪潮】智能体在图像处理领域的技术突破与实践指南
  • 这是我做的网站吗汇云网站建设
  • 【JAVA 进阶】穿越之我在修仙世界学习 @Async 注解(深度解析)