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

【力扣100题】62.滑动窗口最大值

题目描述

给你一个整数数组nums,有一个大小为k的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的k个数字。滑动窗口每次只向右移动一位。

返回滑动窗口中的最大值。

示例

示例 1: 输入:nums = [1,3,-1,-3,5,3,6,7], k = 3 输出:[3,3,5,5,6,7] 解释: 滑动窗口的位置 最大值 --------------- ----- [1 3 -1] -3 5 3 6 7 3 1 [3 -1 -3] 5 3 6 7 3 1 3 [-1 -3 5] 3 6 7 5 1 3 -1 [-3 5 3] 6 7 5 1 3 -1 -3 [5 3 6] 7 6 1 3 -1 -3 5 [3 6 7] 7 示例 2: 输入:nums = [1], k = 1 输出:[1]

提示:

  • 1 <= nums.length <= 10^5
  • -10^4 <= nums[i] <= 10^4
  • 1 <= k <= nums.length

解题思路总览

方法核心思想时间复杂度空间复杂度备注
单调递减队列维护一个递减的双端队列O(n)O(k)推荐解法
暴力法每次遍历窗口找最大值O(n*k)O(1)会超时
堆/优先队列用大顶堆存储窗口元素O(n log k)O(k)不够优
分块 + 预处理RMQ/稀疏表O(n)O(n)预处理复杂

一、核心解法:单调递减队列(双端队列)

核心思想

使用一个双端队列,维护窗口内的元素,保证队列从左到右是递减的。

  • 队列中存储数组元素的下标
  • 队列头是当前窗口的最大值
  • 新元素进来时,把比它小的都pop掉(因为它们不可能成为最大值)
nums = [1,3,-1,-3,5,3,6,7], k = 3 滑动窗口过程: 窗口 [1,3,-1]: 3 入队,把比 3 小的 1 pop 掉 队列: [3, -1](存放下标:[1,2]) 最大值: nums[1] = 3 窗口 [3,-1,-3]: -1 入队,不影响(比 3 小) -3 入队,不影响 队列: [3, -1, -3](存放下标:[1,2,3]) 最大值: nums[1] = 3 窗口 [-1,-3,5]: 5 入队,把比 5 小的 -1, -3 pop 掉 队列: [5](存放下标:[4]) 最大值: nums[4] = 5 注意:3 已经不在窗口内了(因为 3 的下标是 1,窗口左边界是 2)

关键洞察

单调递减队列的核心: 1. 新元素进来时,从后往前pop掉所有比它小的元素 - 因为那些元素永远不可能成为最大值(被新元素挡住了) 2. 检查队头是否还在窗口内 - 如果队头的下标 < left,说明它已经不在窗口内了,需要pop掉 3. 队头永远是当前窗口的最大值

图解

nums = [1,3,-1,-3,5,3,6,7], k = 3 初始状态: deque = [] i=0, nums[0]=1: 1 入队,从后pop掉比1小的元素(无) deque = [0] // 存放下标 left = 0 + 3 - 1 = 2,当前窗口还不够3个,不记录答案 i=1, nums[1]=3: 3 入队,从后pop掉比3小的:1 deque = [1] i=2, nums[2]=-1: -1 入队,不pop(-1 < 3) deque = [1,2] left = 2, deque[0]=1 >= 0,有效 记录答案: ans[2] = nums[1] = 3 i=3, nums[3]=-3: -3 入队,不pop deque = [1,2,3] left = 3, deque[0]=1 < 3,无效,pop_front deque = [2,3] 记录答案: ans[3] = nums[2] = -1 i=4, nums[4]=5: 5 入队,从后pop掉比5小的:-1, -3 deque = [1,4] left = 4, deque[0]=1 < 4,无效,pop_front deque = [4] 记录答案: ans[4] = nums[4] = 5 i=5, nums[5]=3: 3 入队,不pop(3 < 5) deque = [4,5] left = 5, deque[0]=4 < 5,无效,pop_front deque = [5] 记录答案: ans[5] = nums[5] = 3 i=6, nums[6]=6: 6 入队,从后pop掉比6小的:3 deque = [4,6] left = 6, deque[0]=4 < 6,无效,pop_front deque = [6] 记录答案: ans[6] = nums[6] = 6 i=7, nums[7]=7: 7 入队,从后pop掉比7小的:6 deque = [4,7] left = 7, deque[0]=4 < 7,无效,pop_front deque = [7] 记录答案: ans[7] = nums[7] = 7 结果: [3,3,5,5,6,7]

二、算法流程图

输入: nums = [1,3,-1,-3,5,3,6,7], k = 3 初始化: deque = [] ans = [] left = i - k + 1 (窗口左边界) 遍历: i=0: nums[0]=1 deque: [0] left = -2 (窗口未形成) i=1: nums[1]=3 pop: 0 (1 < 3) deque: [1] left = -1 (窗口未形成) i=2: nums[2]=-1 deque: [1,2] left = 0 deque[0]=1 >= 0, ans.push(3) i=3: nums[3]=-3 deque: [1,2,3] left = 1 deque[0]=1 < 1, pop_front -> [2,3] ans.push(-1) i=4: nums[4]=5 pop: 2,3 (5 > -1, 5 > -3) deque: [1,4] left = 2 deque[0]=1 < 2, pop_front -> [4] ans.push(5) i=5: nums[5]=3 deque: [4,5] left = 3 deque[0]=4 < 3, pop_front -> [5] ans.push(3) i=6: nums[6]=6 pop: 5 (6 > 3) deque: [4,6] left = 4 deque[0]=4 < 4, pop_front -> [6] ans.push(6) i=7: nums[7]=7 pop: 6 (7 > 6) deque: [4,7] left = 5 deque[0]=4 < 5, pop_front -> [7] ans.push(7) 输出: [3,3,5,5,6,7]

三、完整代码实现

classSolution{public:vector<int>maxSlidingWindow(vector<int>&nums,intk){intn=nums.size();vector<int>ans(n-k+1);deque<int>deque;for(inti=0;i<n;i++){// 1. 新元素进来,从后往前pop掉所有比它小的while(!deque.empty()&&nums[deque.back()]<=nums[i]){deque.pop_back();}deque.push_back(i);// 2. 检查队头是否还在窗口内intleft=i-k+1;// 窗口左边界if(deque.front()<left){deque.pop_front();}// 3. 记录答案(窗口形成后才有答案)if(i>=k-1){ans[left]=nums[deque.front()];}}returnans;}};

四、逐行解析

deque<int>deque;
  • 双端队列,存储数组元素的下标
  • 从左到右按递减顺序排列
  • 队头永远是当前窗口最大值
while(!deque.empty()&&nums[deque.back()]<=nums[i]){deque.pop_back();}
  • 新元素 nums[i] 比队尾元素大
  • 队尾元素不可能成为最大值了(被 nums[i] 挡住了)
  • 从后 pop 掉所有比 nums[i] 小的元素
deque.push_back(i);
  • 将当前下标加入队尾
intleft=i-k+1;
  • 计算当前窗口的左边界
  • 窗口是 [i-k+1, i]
if(deque.front()<left){deque.pop_front();}
  • 检查队头是否在窗口内
  • 如果队头的下标小于左边界,说明已经不在窗口内了,pop掉
if(i>=k-1){ans[left]=nums[deque.front()];}
  • 当 i >= k-1 时,窗口才形成
  • 队头就是当前窗口的最大值

五、单调递减队列的原理

为什么从后pop? 设当前队列: [idx1, idx2, ..., idxm] (对应 nums 值递减) 新元素 nums[i] 入队: 如果 nums[i] > nums[idxm]: idxm 不可能成为最大值了(nums[i] 比它大,且 nums[i] 更晚被移出) pop idxm 继续检查 idxm-1,直到队列为空或 nums[i] <= nums[idx] 为什么从后pop而不是从前往后? 因为我们要维护递减顺序: - 队头是最大值 - 队尾是最近加入的 从后pop是因为: - 最近加入的元素最有可能被新元素"挡住" - 较早加入的元素在窗口内存在时间更长

六、与优先队列对比

维度单调递减队列优先队列(堆)
时间复杂度O(n)O(n log k)
空间复杂度O(k)O(k)
维护方式队列单调性堆的有序性
过期元素处理需要检查并pop需要标记并跳过
特点每元素最多入队出队各一次每元素入堆出堆各一次

七、复杂度分析

方法时间复杂度空间复杂度备注
单调递减队列O(n)O(k)推荐
优先队列O(n log k)O(k)不够优
暴力法O(n*k)O(1)会超时
分块预处理O(n)O(n)预处理复杂

详细分析:

时间复杂度: 每个元素最多入队一次、出队一次 总计:O(2n) = O(n) 空间复杂度: 双端队列最多存 k 个元素(下标) O(k)

八、边界情况分析

情况处理方式
k = 1每个窗口只有一个元素,答案就是数组本身
k = n只有一个窗口,答案是整个数组的最大值
有重复元素从后pop时用 <= 而不是 <,保证只pop掉比新元素小的
全是负数正常处理,队头永远是窗口最小值(相对最大)

示例:k = 1

nums = [1, 3, -1], k = 1 i=0: deque=[0], left=0, i>=0, ans[0]=nums[0]=1 i=1: deque=[1], left=1, i>=0, ans[1]=nums[1]=3 i=2: deque=[2], left=2, i>=0, ans[2]=nums[2]=-1 输出: [1, 3, -1]

九、面试追问 FAQ

问题回答要点
Q: 为什么用双端队列而不是单端队列?需要从队头取最大值,也需要从队尾入新元素、pop小元素,两端都要操作
Q: 为什么从后pop而不是从前往后?从后pop可以维护递减顺序,且不影响队头的最大值
Q: 如何处理窗口过期元素?检查队头下标是否小于窗口左边界,小于则pop掉
Q: 为什么条件是 <= 而不是 <?等于时也要pop,因为新元素在后面,旧的先过期
Q: 时间复杂度为什么是 O(n)?每个元素最多入队一次、出队一次,总操作 2n 次
Q: 能否用其他数据结构?可以用堆,但时间复杂度 O(n log k),不够优

十、相关题目

题目编号题目名称难度核心差异
239滑动窗口最大值困难基础题,单调队列
剑指 Offer 59滑动窗口的最大值困难同本题
1696跳跃游戏 VI中等单调队列 + DP
1438绝对差不超过 K 的最长子数组中等单调队列记录最大最小
862和至少为 K 的最短子数组困难单调队列 + 前缀和
480滑动窗口中位数困难滑动窗口 + 有序集合

十一、总结

要点内容
核心思想单调递减队列,队头是当前窗口最大值
入队操作从后pop掉所有比新元素小的,然后入队
出队操作检查队头是否在窗口内,不在则pop
时间复杂度O(n)(每元素最多入队出队各一次)
空间复杂度O(k)(队列最多存 k 个元素)
关键点deque 存下标而非值,便于判断过期
易错点<= vs < 的选择

滑动窗口最大值是单调队列的经典应用,通过维护一个递减的队列,实现了 O(n) 时间复杂度的算法。核心思想是:把不可能成为最大值的元素从队尾pop掉,队头自然就是最大值。


http://www.jsqmd.com/news/900015/

相关文章:

  • 微信推文发布前必做的4项AI校验:错别字、敏感词、传播力、转化漏斗——ChatGPT自动化实现
  • 开发团队如何通过Taotoken实现API密钥的统一管理与审计
  • AI产品经理学习汇总
  • DeepSeek总结的使用实体-组件-系统和基于存在性处理进行Python编程7-8
  • 2026年上海/贵阳门窗厂家推荐榜单:系统门窗、平开/推拉门窗品质与工艺深度解析 - 品牌企业推荐师(官方)
  • 2026年工业气体/特种气体厂家实力榜单:液氮液氩液氧高纯气体及稀有气体供应商深度推荐 - 品牌企业推荐师(官方)
  • 03、单线通讯—SIF协议在资源受限MCU中的定时器驱动实现与优化
  • 告别PyTorch卡顿:树莓派5从YOLOv5迁移到YOLOv8+ncnn的完整踩坑实录
  • 2026年5月更新江苏无尘室净化空调系统:一体化服务商的深度选择指南 - 2026年企业资讯
  • 【小白零基础】 OpenClaw2.7.5 Windows 快速部署方法(包含安装包)
  • 学术创作提速新思路:okbiye 智能论文撰写模块,适配高校全品类论文创作需求
  • 2026年5月长春数字科技职业大专选校指南:深度解析长春数字科技职业学院 - 2026年企业资讯
  • YOLO 数据集构建与效果验证实战指南
  • 用STM32F103C8T6做个可调电源:从原理图到代码的保姆级教程(含LCD1602显示与过流保护)
  • 实战复盘:我用Python+Appium给公司老旧的Win32客户端做自动化回归测试,踩了这些坑
  • 基于树莓派Ubuntu Mate与PX4的UDP通信:搭建QGC地面站远程监控系统
  • 从单体AI代理到协调者模式:架构演进提升任务完成率与可维护性
  • 避坑指南:Unity中用C# DateTime处理时间,别忘了时区和性能这两件事
  • 具身智能(Embodied AI)
  • 钉钉消息防撤回补丁PC版:终极解决方案,让你不再错过任何重要信息
  • 手把手教你用Python免费调用阿里云通义千问1.8B模型API(附完整代码)
  • 谷歌seo主页优化做什么?图片Alt标签加这3个词最管用
  • RAG系统静默失败:诊断、防御与全链路质量保障实战
  • 2026年广告物料制作厂家推荐榜:写真/KT板/PVC板/雕刻/条幅/车贴/喷绘加工优质品牌深度解析 - 品牌企业推荐师(官方)
  • Qt ItemDataRole深度解析:从核心角色到界面定制
  • 别再死磕单级PID了!PX4固定翼姿态控制器里的串级PID,为什么是双回路的?
  • 瑞芯微RK3588 开发板USB线刷eMMC系统教程
  • 2025-2026年尚百年全铝家居联系电话:电话查询前请核实产品特性与订购流程 - 品牌推荐
  • C++ 高性能编程:如何用 AVX2 手写达到硬件理论极限的向量点积算子
  • 别再为OpenMV串口传图卡顿发愁了!实测对比STM32调试器与TTL模块,教你选对硬件(附921600波特率避坑指南)