news 2026/5/16 16:50:13

单调队列+滑动窗口

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
单调队列+滑动窗口

对应力扣239滑动窗口的最大值

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

暴力解法:

  • 设置左右指针形成固定长度 k的窗口(左指针left,右指针right=left+k-1);
  • 遍历窗口内[left, right]的 k 个元素,找到并记录最大值;
  • 左右指针同时右移 1 位,重复步骤 2,直到窗口滑出数组末尾;
  • 最终将所有窗口的最大值组成数组返回。

时间复杂度为O(n×k)n为数组长度,k为窗口长度

/** * @param {number[]} nums * @param {number} k * @return {number[]} */ var maxSlidingWindow = function(nums, k) { const res = []; const n = nums.length; let left = 0; // 右指针最大为n-1,窗口左边界最大为n-k while (left <= n - k) { let right = left + k - 1; let max = nums[left]; // 遍历当前窗口,找最大值(你的核心思路) for (let i = left; i <= right; i++) { max = Math.max(max, nums[i]); } res.push(max); left++; // 指针右移,窗口滑动 } return res; };

为什么暴力法效率低?(重复计算是关键)

暴力法的致命问题是存在大量重复计算:窗口每次只右移 1 位,相邻两个窗口有 k-1 个元素是重叠的,但暴力法会重新遍历全部 k 个元素找最大值,浪费了重叠部分的计算结果

单调队列(双端队列 Deque)

要解决重复计算问题,核心是用一个特殊队列记录「窗口内可能成为最大值的元素下标」,让队列保持单调递减,这样队列队首永远是当前窗口的最大值下标,窗口滑动时只需维护这个队列,无需遍历窗口。

先理解:单调递减队列的核心规则

队列中存储的是nums 的元素下标(而非值),且满足:下标对应的值从队首到队尾严格单调递减(比如队列里的下标对应值是 [5,3,-1],而非 [3,5,-1])。

队列的3 个核心维护操作(窗口滑动时执行):

  1. 去尾:当新元素进入窗口时,若新元素值 ≥ 队尾下标对应的值,则队尾出队(因为该队尾元素在窗口内,不可能成为后续任何窗口的最大值,直接淘汰),重复此操作直到队列单调递减;
  2. 入队:将新元素的下标加入队尾,此时队列仍保持单调递减;
  3. 删头:若队首下标 ≤ 窗口左边界 - 1(说明队首元素已滑出窗口),则队首出队,保证队首始终在窗口内。
最优解核心步骤(全程只需遍历数组 1 次)
  1. 初始化双端队列 deque(存储下标)、结果数组 res;
  2. 遍历数组 nums 的每个元素,下标为i
    • 步骤 1:去尾→ 维护队列单调递减,淘汰比当前元素小的队尾;
    • 步骤 2:入队→ 将i加入 deque 队尾;
    • 步骤 3:删头→ 移除滑出窗口的队首下标;
    • 步骤 4:记录最大值→ 当i ≥ k-1(说明第一个窗口已形成),将nums[deque[0]](队首是窗口最大值下标)加入 res;
  3. 遍历结束,res 即为所有窗口的最大值数组。

用数组模拟双端队列(shift()删队首、pop()删队尾、push()入队尾)

/** * @param {number[]} nums * @param {number} k * @return {number[]} */ var maxSlidingWindow = function(nums, k) { const res = []; const deque = []; // 双端队列,存储nums的下标,保持单调递减 const n = nums.length; for (let i = 0; i < n; i++) { // 操作1:去尾 - 维护单调递减,淘汰比当前元素小的队尾 while (deque.length && nums[i] >= nums[deque[deque.length - 1]]) { deque.pop(); } // 操作2:入队 - 加入当前元素下标 deque.push(i); // 操作3:删头 - 移除滑出窗口的队首(窗口左边界为i-k+1) while (deque[0] <= i - k) { deque.shift(); } // 操作4:记录最大值 - 第一个窗口形成后(i>=k-1),队首即为当前窗口最大值 if (i >= k - 1) { res.push(nums[deque[0]]); } } return res; };

为什么左边界是i-k+1

你问的这个问题是滑动窗口的核心边界计算规则i-k+1当前遍历下标i作为窗口右边界时,窗口对应的左边界下标,核心推导基于「窗口长度固定为k+ 数组下标从 0 开始」两个前提,下面用大白话 + 数学推导 + 例子讲透,保证一看就懂!

一、核心前提(先记死,所有推导的基础)

  1. 力扣 239 题是固定长度滑动窗口,窗口内元素的个数严格等于k(比如k=3,窗口永远包含 3 个元素);
  2. 数组 / 字符串的下标从 0 开始(这是编程的通用规则,也是边界计算的关键);
  3. 我们遍历数组的下标i始终作为当前窗口的「右边界」(窗口的最后一个元素的下标)。

二、数学公式推导(10 秒看懂,最直观)

我们的目标是:已知窗口右边界下标i+ 窗口长度k,求窗口左边界下标left

第一步:先想「连续数字的个数」计算规则

对于任意连续的下标区间[left, i](left ≤ i),区间内的元素个数计算公式是:元素个数=i−left+1✅ 验证(下标从 0 开始):

  • 区间[0,2]2-0+1=3个元素(0、1、2),正确;
  • 区间[1,3]3-1+1=3个元素(1、2、3),正确;
  • 区间[2,4]4-2+1=3个元素(2、3、4),正确。
第二步:结合「窗口长度 = k」推导左边界

因为固定窗口的元素个数必须等于k,所以代入公式得:k=i−left+1将公式移项求解left(小学一元一次方程):left=i−k+1

这就是i-k+1数学由来,没有任何复杂逻辑,纯粹是「下标从 0 开始」的个数计算规则推导结果。

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

一套可复用的高质量特征挖掘方法论

同样的数据,为什么别人能挖出金矿,你只能挖出噪音?“数据我们已经有很多了,但为什么模型就是不准?”“每天跑那么多特征,上线后效果还不如业务直觉…”,“用户标签打了一堆,营销时还是像在撒网…”    如果你或你的团队也曾被这些问题困扰,你不是一个人。数据泛滥时…

作者头像 李华
网站建设 2026/5/6 13:21:49

加密账本UI设计

追求更高的安全性&#xff0c;我觉得还是有需求的

作者头像 李华
网站建设 2026/5/5 2:51:07

PolarDB-X 集群暂停 / 恢复完整运维文档

目录标题PolarDB-X 集群暂停 / 恢复完整运维文档一、背景与基本认知&#xff08;非常重要&#xff09;1️⃣ PolarDB-X 没有传统意义的 shutdown二、组件角色与资源形态&#xff08;基于你现场&#xff09;1️⃣ 组件与 K8S 资源映射三、组件依赖关系&#xff08;铁律&#xff…

作者头像 李华
网站建设 2026/5/11 10:27:41

Java计算机毕设之基于springboot的个性化推荐电商平台的设计与实现基于SpringBoot的网上购物商城设计与实现(完整前后端代码+说明文档+LW,调试定制等)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/5/9 0:42:25

C++的函数是否可以做到完全覆盖Linux和windows的跨平台

答案是无法做到完全覆盖*&#xff0c;但可以通过C的语言特性和工程设计&#xff0c;实现高程度的跨平台系统功能替代/封装——核心原因是Linux和Windows的底层系统架构、API体系存在本质性差异&#xff0c;C本身无原生能力抹平这些差异&#xff0c;需通过针对性的工程方案实现跨…

作者头像 李华