嗯,查询滑动窗口最大值的这4种方法不错....


作者 | 王磊

来源 | Java中文社群(ID:javacn666)

转载请联系授权(微信ID:GG_Stone)

本文已收录至 Github《小白学算法》系列:https://github.com/vipstone/algorithm

这是一道比较基础的算法题,涉及到的数据结构也是我们之前讲过的,我这里先买一个关子。这道面试题最近半年在亚马逊的面试中出现过 28 次,在字节跳动中出现过 7 次,数据来源于 LeetCode。

我们先来看题目的描述。

题目描述

给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值。

示例:

输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3 输出: [3,3,5,5,6,7]

提示:你可以假设 k 总是有效的,在输入数组不为空的情况下,1 ≤ k ≤ 输入数组的大小。

LeetCode:https://leetcode-cn.com/problems/hua-dong-chuang-kou-de-zui-da-zhi-lcof/

题目解析

上面的题目看不懂?没关系,接下来来看这幅图可以清楚的描述这道题:从上述图片可以看出,题目的意思为:给定一个数组,每次查询 3 个元素中的最大值,数量 3 为滑动窗口的大小,之后依次向后移动查询相邻 3 个元素的最大值。图片中的原始数组为 [1,3,-1,-3,5,3,6,7],最终滑动窗口的最大值为 [3,3,5,5,6,7]

看到这个题之后,我们的第一直觉就是暴力解法,用两层循环依次查询滑动窗口的最大值,实现代码如下。

实现方法 1:暴力解法

暴力解法的实现思路和实现代码很直观,如下所示:

class Solution {public int[] maxSlidingWindow(int[] nums, int k) {// 非空判断if (nums == null || k <= 0) return new int[0];// 最终结果数组int[] res = new int[nums.length - k + 1];for (int i = 0; i < res.length; i++) {// 初始化最大值int max = nums[i]; // 循环 k-1 次找最大值for (int j = i + 1; j < (i + k); j++) {max = (nums[j] > max) ? nums[j] : max;}res[i] = max;}return res;}
}

把以上代码提交至 LeetCode,执行结果如下:从上述结果可以看出,虽然代码通过了测试,但执行效率却很低,这种代码是不能应用于生产环境中的,因此我们需要继续找寻新的解决方案。

实现方法 2:改良版

接下来我们稍微优化一下上面的方法,其实我们并不需要每次都经过两层循环,我们只需要一层循环拿到滑动窗口的最大值(之前循环元素的最大值),然后在移除元素时,判断当前要移除的元素是否为滑动窗口的最大值,如果是,则进行第二层循环来找到新的滑动窗口的最大值,否则只需要将最大值和新增的元素比较大小即可,实现代码如下:

class Solution {public int[] maxSlidingWindow(int[] nums, int k) {// 非空判断if (nums == null || k <= 0) return new int[0];// 最终结果数组int[] res = new int[nums.length - k + 1];// 上一轮循环移除的值int r = -Integer.MAX_VALUE; // 滑动窗口最大值(初始化)int max = r; for (int i = 0; i < res.length; i++) {// 1.判断移除的值,是否为滑动窗口的最大值if (r == max) {// 2.移除的是滑动窗口的最大值,循环找到新的滑动窗口的最大值max = nums[i]; // 初始化最大值// 循环找最大值for (int j = i + 1; j < (i + k); j++) {max = Math.max(max, nums[j]);}} else {// 3.只需要用滑动窗口的最大值和新增值比较即可max = Math.max(max, nums[i + k - 1]);}// 最终的返回数组记录res[i] = max;// 记录下轮要移除的元素r = nums[i];}return res;}
}

把以上代码提交至 LeetCode,执行结果如下:从上述结果可以看出,改造之后的性能基本已经符合我的要求了,那文章开头说过这道题还可以使用我们之前学过的数据结构?那它说的是什么数据结构呢?

其实我们可以使用「队列」来实现这道题目,它的实现思路也非常简单,甚至比暴力解法更加方便,接下来我们继续往下看。

实现方法 3:优先队列

这个题的另一种经典的解法,就是使用最大堆的方式来解决,最大堆的结构如下所示:最大堆的特性是堆顶是整个堆中最大的元素

我们可以将滑动窗口的值放入最大堆中,这样利用此数据结构的特点(它会将最大值放到堆顶),因此我们就可以直接获取到滑动窗口的最大值了,实现代码如下:

class Solution {public int[] maxSlidingWindow(int[] nums, int k) {// 非空判断if (nums == null || k <= 0) return new int[]{};// 最终结果数组int[] res = new int[nums.length - k + 1];// 优先队列PriorityQueue<Integer> queue = new PriorityQueue(res.length, new Comparator<Integer>() {@Overridepublic int compare(Integer i1, Integer i2) {// 倒序排列(从大到小,默认是从小到大)return i2 - i1;}});// 第一轮元素添加for (int i = 0; i < k; i++) {queue.offer(nums[i]);}res[0] = queue.peek();int last = nums[0]; // 每轮要移除的元素for (int i = k; i < nums.length; i++) {// 移除滑动窗口之外的元素queue.remove(last);// 添加新元素queue.offer(nums[i]);// 存入最大值res[i - k + 1] = queue.peek();// 记录每轮要移除的元素(滑动窗口最左边的元素)last = nums[i - k + 1];}return res;}
}

代码解读

从上述代码可以看出:最大堆在 Java 中对应的数据结构就是优先级队列 PriorityQueue,但优先级队列默认的排序规则是从小到大进行排序的,因此我们需要创建一个 Comparator 来改变一下排序的规则(从大到小进行排序),之后将滑动窗口的所有元素放入到优先级队列中,这样我们就可以直接使用 queue.peek() 拿到滑动窗口的最大值了,然后再循环将滑动窗口的边缘值移除掉,从而解决了本道题目。

把以上代码提交至 LeetCode,执行结果如下:

PS:从上面的执行结果可以看出,使用优先队列的执行效率很低,这是因为每次插入和删除都需要重新维护最大堆的元素顺序,因此整个执行的效率就会很低。

实现方法 4:双端队列

除了优先队列之外,我们还可以使用双端队列来查询滑动窗口的最大值,它的实现思路和最大堆的实现思路很像,但并不需要每次在添加和删除时进行元素位置的维护,因此它的执行效率会很高。

双端队列实现思路的核心是将滑动窗口的最大值始终放在队首位置(也就是队列的最左边),将小于最大值并在最大值左边(队首方向)的所有元素删除。这个也很好理解,因为这些相对较小的值既没有最大值大,又在最大值的前面,也就是它们的生命周期比最大值还短,因此我们可以直接将这些相对较小的元素进行删除,如下图所示:

像以上这种情况下,我们就可以将元素 1 和元素 2 删掉。

双端队列实现查询滑动窗口最大值的流程分为以下 4 步:

  1. 移除最左边小于最大值的元素(保证滑动窗口的最大值在队首位置);

  2. 从队尾向前依次移除小于当前要加入到队列元素的值(淘汰小值且生命周期短的元素);

  3. 将新元素加入到队列末尾;

  4. 将最大值加入到最终结果的数组中。

实现代码如下:

class Solution {public int[] maxSlidingWindow(int[] nums, int k) {// 非空判断if (nums == null || k <= 0) return new int[0];// 最终结果数组int[] res = new int[nums.length - k + 1];// 存储的数据为元素的下标ArrayDeque<Integer> deque = new ArrayDeque();for (int i = 0; i < nums.length; i++) {// 1.移除左边超过滑动窗口的下标if (i >= k && (i - k) >= deque.peek()) deque.removeFirst();// 2.从最后面开始移除小于 nums[i] 的元素while (!deque.isEmpty() && nums[deque.peekLast()] < nums[i])deque.removeLast();// 3.下标加入队列deque.offer(i);// 4.将最大值加入数组int rindex = i - k + 1;if (rindex >= 0) {res[rindex] = nums[deque.peek()];}}return res;}
}

把以上代码提交至 LeetCode,执行结果如下:

从上述结果可以看出,双端队列相比于优先级队列来说,因为无需重新计算并维护元素的位置,所以执行效率还是挺高的。

总结

本文我们通过 4 种方式实现了查找滑动窗口最大值的功能,其中暴力解法通过两层循环来实现此功能,代码最简单但执行效率不高,而通过最大堆也就是优先队列的方式来实现(本题)虽然比较省事,但执行效率不高。因此我们可以选择使用双端队列或改良版的代码来实现查询滑动窗口的最大值。


往期推荐

真不错,图解Java中的5大队列!


23张图!万字详解「链表」,从小白到大佬!


队列实现栈的3种方法,全都击败了100%的用户!


关注我,每天陪你进步一点点!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/545252.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SVN客户端使用

SVN客户端使用1. 复制服务端URL2. 在客户端电脑新建文件夹用于存储版本代码3. 右击空白处&#xff0c;checkout4. 进入trunk目录&#xff0c;即可尝试新建文件并上传到服务端4.其他客户端更新版本5.解决代码冲突期间由于之前登陆过并设置为记住密码&#xff0c;所以整个过程并没…

25 张图,1.4 w字!彻底搞懂分布式事务原理

本文提纲如下&#xff1a;0. 前言1. 单数据源事务 & 多数据源事务2. 常见分布式事务解决方案2.1. 分布式事务模型2.2. 二将军问题和幂等性2.3. 两阶段提交&#xff08;2PC&#xff09; & 三阶段提交&#xff08;3PC&#xff09;方案2.4. TCC 方案2.5. 事务状态表方案2.…

des加密密码补位_密码学中的数据加密标准(DES)

des加密密码补位This is a Data Encryption Standard that is the asymmetric key generation for the encryption of digital data in cryptography. Therefore, its short key length of 56 bits of character criticized from the beginning or starting makes it too insec…

报告老板:这次的缓存事故是这样的...

事故背景公司最近安排了一波商品抢购活动&#xff0c;由于后台小哥操作失误最终导致活动效果差&#xff0c;被用户和代理商投诉了。老板让我带同事们一起复盘这次线上事故。什么原因造成的&#xff1f;抢购活动计划是零点准时开始&#xff0c;22&#xff1a;00 运营人员通过后台…

隐式转换

2019独角兽企业重金招聘Python工程师标准>>> 1&#xff1a;隐式转换应用 1.1 隐式转换为期望类型 隐式转换为期望类型是编译器会使用隐式操作的第一个地方。一旦编译器看到了X&#xff0c;但是需要Y&#xff0c;就会检查从X到Y的隐式转换函数。例如&#xff1a; val…

双“11”搞促销?用贪心算法来盘他!

作者 | 王磊来源 | Java中文社群&#xff08;ID&#xff1a;javacn666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone&#xff09;这几年商家为了刺激消费是变着花样的推出各种各样的活动&#xff0c;以某多多为首的运营式电商更是让我们看到了营销的无限“…

AndroidStudio使用入门

AndroidStudio使用入门1_AndroidStudio activity的基本使用1.1_MainActivity和activity_main的初识1.2_Activity的清单文件简介1.3_几种重要文件的介绍1.4_基本布局的认识与使用1.4.1_RelativeLayout(相对布局)1.4.2_线性布局2_访问资源的方式2.1_java访问资源的方式2.2_xml访问…

面试官:你说说互斥锁、自旋锁、读写锁、悲观锁、乐观锁的应用场景?

前言生活中用到的锁&#xff0c;用途都比较简单粗暴&#xff0c;上锁基本是为了防止外人进来、电动车被偷等等。但生活中也不是没有 BUG 的&#xff0c;比如加锁的电动车在「广西 - 窃格瓦拉」面前&#xff0c;锁就是形同虚设&#xff0c;只要他愿意&#xff0c;他就可以轻轻松…

2万字,看完这篇才敢说自己真的懂线程池!

前言 线程池可以说是 Java 进阶必备的知识点了&#xff0c;也是面试中必备的考点&#xff0c;可能不少人看了一些文章后能对线程池工作原理说上一二&#xff0c;但这还远远不够&#xff0c;如果碰到比较有经验的面试官再继续追问&#xff0c;很可能会被吊打&#xff0c;考虑如下…

西南大学校园GIS平台

原文:西南大学校园GIS平台系统架构是B/S,开发语言是C#、silverlight&#xff0c;开发平台是.NET&#xff0c;数据库为sqlserver&#xff0c;这是我读研究生时候自己做的作品&#xff0c;以自己的母校为地图&#xff0c;进行GIS相关的功能分析&#xff0c;核心的模块有&#xff…

Android studio小问题解决

1_代码识别不出来问题 2_项目SDK与本地不匹配 先查看项目的SDK 增加本地SDK

2万字长文包教包会 JVM 内存结构

点击蓝色“Java中文社群”关注我哟加个“星标”&#xff0c;一起成长&#xff0c;做牛逼闪闪的技术人JVM ≠ Japanese Videos Man写这篇的主要原因呢&#xff0c;就是为了能在简历上写个“熟悉JVM底层结构”&#xff0c;另一个原因就是能让读我文章的大家也写上这句话&#xf…

虹软安卓人脸识别初学

1_下载SDK 进入虹软官网&#xff1a; 点击人脸识别SDK 进入开发者中心&#xff08;注册登录&#xff09; 新建应用并添加SDK 输入相应信息&#xff0c;确认 下载sdk 2_run示例代码注意事项 跑不起来的解决方法&#xff08;SDK版本不一致&#xff09; 3_激活并使用

css圆在中心根据宽度缩放_根据CSS中的容器宽度重新缩放字体

css圆在中心根据宽度缩放Introduction: 介绍&#xff1a; Dealing with fonts is a very interesting thing to do as fonts bring out the appearance of your website or a web page so you must choose the ideal fonts for your website or web page that helps in making…

Java中不可或缺的59个小技巧,贼好用!

来源&#xff1a;https://blog.dogchao.cn/?p70《Effective JavaJava》名著&#xff0c;必读。如果能严格遵从本文的原则&#xff0c;以编写API的质量来苛求自己的代码&#xff0c;会大大提升编码素质。以下内容只记录了我自己整理的东西&#xff0c;还是建议读原文。为了聚焦…

第一次使用Sourcetree成功上传gitee记录

第一次使用Sourcetree成功上传gitee记录2_克隆gitee仓库到本地1_设置密钥公钥3_上传本地工作区进而上传到gitee4_最后一步&#xff0c;将文件从本地master提交到gitee5_小问题汇总5.1_git远端打不开5.2_当有多个仓库在使用时设置多个密钥公钥5.3_账户公钥和仓库公钥使用ssh密钥…

SUBSTR函数的使用

http://docs.oracle.com/cd/E11882_01/server.112/e41084/functions181.htm#i87066Substr语法&#xff1a;substr函数返回字符的部分&#xff0c;从postition开始定位&#xff0c;返回可选的字符长度substring_length。substr根据char字符集的字节数来计算长度。substrb则使用位…

坑爹的 Lombok,把我害惨了!

来源&#xff1a;juejin.im/post/6881432532332576781序言去年在项目当中引入了Lombok插件&#xff0c;着实解放了双手&#xff0c;代替了一些重复的简单工作(Getter,Setter,toString等方法的编写)&#xff0c;但是&#xff0c;在使用的过程当中&#xff0c;也发现了一些坑&…

数据结构学习笔记(六)链表算法题

假期结束&#xff0c;看点题目。 第一题 问题 设顺序表用数组A[]表示&#xff0c;表中元素存储在数组下标1~mn的范围内&#xff0c;前m个元素递增有序&#xff0c;后n个元素递增有序&#xff0c;设计一个算法&#xff0c;使得整个顺序表有序。 &#xff08;1&#xff09;给出算…

安卓第一次搭建C/S架构

1_数据库 2_服务端 服务端简单搭建准入门 使用json&#xff0c;导入jar包复制这段内容后打开百度网盘手机App&#xff0c;操作更方便哦 提取码&#xff1a; 3afj 在项目中建一个文件夹并粘贴进去 json与list的互转&#xff1a; import com.alibaba.fastjson.JSON;import j…