news 2026/7/29 3:17:06

滑动窗口最大值算法:单调队列原理与实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
滑动窗口最大值算法:单调队列原理与实现

1. 问题背景与核心挑战

LeetCode 239题"滑动窗口最大值"是算法面试中的经典问题,也是理解滑动窗口和单调队列这两个重要算法思想的绝佳案例。题目要求给定一个整数数组nums和一个整数k,找出每个滑动窗口中的最大值。例如,对于数组[1,3,-1,-3,5,3,6,7]和k=3,输出应为[3,3,5,5,6,7]。

这个问题的难点在于如何高效地维护窗口内的最大值信息。最直观的暴力解法时间复杂度为O(nk),当n和k都很大时(比如n=10^5,k=10^4),这种解法显然无法满足性能要求。我们需要找到一种能在O(n)时间复杂度内解决问题的方案。

2. 单调队列解法详解

2.1 单调队列的设计思想

单调队列是一种特殊的双端队列,它能在O(1)时间内获取队列中的最大值(或最小值),同时保持队列元素的单调性。对于本题,我们需要维护一个单调递减队列,这样队列首部始终是当前窗口的最大值。

单调队列的核心操作包括:

  1. 入队时,从队列尾部开始移除所有小于当前元素的元素,保持队列的单调递减性
  2. 出队时,只有当队首元素等于窗口移出的元素时才真正移除

这种设计保证了队列中的元素既是有序的,又都是"有潜力"成为后续窗口最大值的候选者。

2.2 完整算法实现

from collections import deque def maxSlidingWindow(nums, k): if not nums: return [] result = [] q = deque() for i in range(len(nums)): # 移除超出窗口范围的元素 if q and q[0] == i - k: q.popleft() # 维护单调递减队列 while q and nums[q[-1]] < nums[i]: q.pop() q.append(i) # 当窗口形成后开始记录结果 if i >= k - 1: result.append(nums[q[0]]) return result

这个实现的时间复杂度是O(n),因为每个元素最多被加入和移除队列各一次。空间复杂度是O(k),因为队列中最多存储k个元素。

3. 算法正确性证明

为了验证这个算法的正确性,我们需要考虑以下几点:

  1. 队列始终保持单调递减性质,这意味着队首元素始终是当前窗口的最大值
  2. 窗口滑动时,我们只移除那些已经不在窗口范围内的元素
  3. 新元素加入时,我们移除了所有比它小的元素,因为它们不可能再成为后续窗口的最大值

通过数学归纳法可以严格证明这个算法的正确性。对于任意位置i,队列中存储的都是当前窗口内可能成为最大值的候选元素,且这些元素按照从大到小排列。

4. 边界条件与特殊测试用例

在实际编码中,我们需要特别注意以下边界情况:

  1. 空数组输入:应该返回空数组
  2. k=1的情况:每个窗口就是单个元素,直接返回原数组
  3. k等于数组长度:整个数组就是一个窗口,返回包含最大值的单元素数组
  4. 数组元素全部相同:所有窗口的最大值都相同
  5. 数组严格递增/递减:测试队列的维护是否正确

例如:

  • nums = [], k = 0 → []
  • nums = [1], k = 1 → [1]
  • nums = [1,2,3,4,5], k = 5 → [5]
  • nums = [5,5,5,5,5], k = 2 → [5,5,5,5]
  • nums = [1,2,3,4,5], k = 2 → [2,3,4,5]
  • nums = [5,4,3,2,1], k = 2 → [5,4,3,2]

5. 算法优化与变种

5.1 空间优化

在某些情况下,我们可以直接复用输入数组来存储结果,进一步减少空间使用。但需要注意不要覆盖还需要使用的原始数据。

5.2 处理流数据

如果数据是以流的形式到来的(即无法一次性获取所有数据),我们可以修改算法,在每次新数据到达时立即计算当前窗口的最大值。

5.3 多维度扩展

这个问题可以扩展到二维情况,比如在图像处理中寻找局部区域的最大值。此时可以使用类似的单调队列思想,但需要在行和列两个方向上进行处理。

6. 实际应用场景

滑动窗口最大值算法在以下场景中有重要应用:

  1. 网络流量监控:统计固定时间窗口内的最大流量
  2. 股票分析:计算特定时间范围内的最高股价
  3. 信号处理:提取信号在滑动窗口内的峰值
  4. 计算机视觉:在局部区域内寻找特征最大值
  5. 实时系统:监控系统资源使用的峰值

7. 常见错误与调试技巧

在实现这个算法时,开发者常犯的错误包括:

  1. 忘记处理空输入的情况
  2. 窗口索引计算错误,特别是从0开始还是从1开始的问题
  3. 在维护单调队列时,错误地移除了不该移除的元素
  4. 没有正确处理k=1或k=len(nums)的边界情况

调试时可以:

  1. 打印出每一步的队列状态和当前窗口
  2. 使用小规模的测试用例手动验证
  3. 特别注意循环终止条件和索引边界

8. 性能对比与替代方案

除了单调队列,这个问题还有其他解法:

  1. 最大堆:时间复杂度O(nlogk),因为每次插入和删除堆需要O(logk)时间
  2. 分块预处理:将数组分成大小为k的块,预处理每个块的前缀最大值和后缀最大值,然后组合得到窗口最大值。时间复杂度O(n),但实现较复杂
  3. 线段树或稀疏表:可以处理更一般的区间查询问题,但实现复杂且常数较大

相比之下,单调队列解法在时间和空间复杂度上都是最优的,且实现相对简单。

9. 代码实现细节与优化

在实际编码中,我们可以做以下优化:

  1. 预分配结果数组空间,避免动态扩容
  2. 使用数组而不是双端队列来实现单调队列,在某些语言中可能更快
  3. 对于特别大的k,可以考虑提前终止(如果已经找到整个数组的最大值)

例如,优化后的Python实现:

def maxSlidingWindow(nums, k): if not nums: return [] n = len(nums) result = [0] * (n - k + 1) q = [] for i in range(n): while q and nums[q[-1]] < nums[i]: q.pop() q.append(i) if q[0] == i - k: q.pop(0) if i >= k - 1: result[i - k + 1] = nums[q[0]] return result

10. 扩展思考与相关问题

掌握了滑动窗口最大值后,可以尝试解决以下变种问题:

  1. 滑动窗口最小值:只需将单调递减队列改为单调递增队列
  2. 滑动窗口中位数:需要使用两个堆(最大堆和最小堆)来维护
  3. 滑动窗口统计量:如平均值、标准差等
  4. 多维滑动窗口:如在二维矩阵中滑动子矩阵

LeetCode上相关题目包括:

    1. 滑动窗口中位数
    1. 带限制的子序列和(使用单调队列优化动态规划)
    1. 绝对差不超过限制的最长连续子数组

理解单调队列的思想后,你会发现它还能用于优化某些动态规划问题,如求最大子数组和等。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/29 3:16:37

GetQzonehistory:一键备份QQ空间记忆的数字时光机

GetQzonehistory&#xff1a;一键备份QQ空间记忆的数字时光机 【免费下载链接】GetQzonehistory 获取QQ空间发布的历史说说 项目地址: https://gitcode.com/GitHub_Trending/ge/GetQzonehistory 在数字时代&#xff0c;我们的青春记忆往往散落在社交平台的各个角落。QQ空…

作者头像 李华
网站建设 2026/7/29 3:16:36

Python爬虫实战:从零构建快手批量采集工具,应对动态加载与反爬

1. 从零开始&#xff1a;为什么我们需要批量采集快手内容&#xff1f;如果你是一个内容创作者、市场分析师&#xff0c;或者只是对某个特定话题在快手上的传播情况感到好奇&#xff0c;你可能会发现&#xff0c;手动一个个去翻看、记录视频信息是一件极其低效且痛苦的事情。比如…

作者头像 李华
网站建设 2026/7/29 3:15:06

车载诊断架构 ---关于整车证书认证(Service 29)疑问思考汇总

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 假若你的生活不够好,不够努力,那么,加油努力吧,不要抱怨,起而行,迎头赶上,方是正途。假若你已经拥有很多,却依然活得不快乐,那么,让自己慢…

作者头像 李华
网站建设 2026/7/29 3:15:00

小米Agent团队重磅开源:可像搭乐高一样随便拼的Harness!

此前绝大多数 Agent 开发者会忽视 Harness 调度层&#xff0c;一味堆基座大模型&#xff0c;最近人们发现大模型 Agent 的表现并不只看基座模型&#xff0c;中间那层把模型、提示词、工具、记忆和控制流串起来的运行时 Harness 才是关键。它决定了任务怎么拆、工具怎么调、决策…

作者头像 李华
网站建设 2026/7/29 3:14:25

服创大赛A类赛道实战指南:从组队到答辩的完整项目交付经验

1. 项目概述&#xff1a;一场硬核的“服务外包”实战如果你是一名计算机、软件工程或相关专业的在校生&#xff0c;并且对“用技术解决真实商业问题”这件事抱有热情&#xff0c;那么“中国大学生服务外包创新创业大赛”&#xff08;简称“服创大赛”&#xff09;的A类赛道&…

作者头像 李华
网站建设 2026/7/29 3:13:15

Spring AOP切点表达式详解与实战技巧

1. AOP切点表达式入门指南第一次听说AOP&#xff08;面向切面编程&#xff09;时&#xff0c;我脑海中浮现的是手术台上的场景——医生不需要切开整个身体&#xff0c;只需要在特定部位做微创切口就能完成手术。AOP的切点表达式&#xff08;Pointcut Expression&#xff09;正是…

作者头像 李华