剑指 Offer 59 - I. 滑动窗口的最大值

知识点:队列;滑动窗口;单调

题目描述

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

示例
输入: 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

解法一:滑动窗口+双端队列+单调

滑动窗口总体上分成两类,一类是可变长度的滑动窗口,一类是固定长度的滑动窗口,这道题目就是固定长度的。在遍历元素时,为了保持窗口的大小固定,右侧元素进入窗口后,左侧元素要能够出去。然后直到遍历结束。

想一下刚才的过程,右侧元素进入,左侧元素出去,这不就是双端队列吗?所以这道题目可以借助双端队列来解;

想一下我们经常会遇到求一个队列或者一个窗口一个栈内的最大最小值,怎么求呢,最简单的方法就是遍历这个窗口这个栈,这样时间复杂度就是O(N),有没有办法能在O(1)时间内获得一个栈或者一个窗口内的最值呢,这其实就是剑指offer30题,比如获取一个栈内的最小值,我们可以采用一个辅助栈,这个辅助栈有一个最大的特点就是单调的,也就是我们俗称的单调栈。比如我们维持一个单调递减栈,如果当前值比栈顶元素大,那就不要了,因为我们最后只获取最小值,如果比当前栈顶小,那就入栈,也就是更新了最小值;这样就可以在O(1)的时间内获得栈内最小值了,因为最小值就是辅助栈的栈顶。

这道题目也类似啊,我们需要获得窗口内的最大值,这不就是一个双端队列的最大值吗,所以我们要维持一个单调递减的双端队列,如何实现呢,每次入队前,判断此值与队尾元素的大小,小于的话就入队,这样就维持了一个单调递减队列;如果元素比队尾值要大,那就要将队尾元素出队了,因为我们只关注大的值,可不能把这个大值错过了,这里面的小值就不用管了。

比如[5,3,4], 4要入队的时候发现3比其小,所以3从队尾出去,4入队;

class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int len = nums.length;
if(len == 0) return nums;
int[] res = new int[len-k+1];
Deque<Integer> deque = new LinkedList<>(); //双端队列;
int index = 0;
//未形成窗口;
for(int i = 0; i < k; i++){
while(!deque.isEmpty() && nums[i] > deque.peekLast()){
deque.removeLast(); //保证单调递减队列;
}
deque.offerLast(nums[i]);
}
res[index++] = deque.peekFirst(); //队首始终是最大的;
//滑动窗口;
for(int i = k; i < len; i++){ //i代表当前窗口最后一个元素的索引;
//保证队内只含有窗口内的元素,所以当窗口的前一个元素等于队首的时候,要将队首出队;
if(deque.peekFirst() == nums[i-k]){
deque.removeFirst();
}
while(!deque.isEmpty() && nums[i] > deque.peekLast()){
deque.removeLast(); //保证单调递减队列;
}
deque.offerLast(nums[i]);
res[index++] = deque.peekFirst(); //队首始终是当前窗口内最大的;
}
return res;
}
}

解法二:滑动窗口+单调

上面的做法我们每次入队的是元素的值,本质上就是用双端队列来模拟了窗口的滑动,双端队列是单调队列;

其实我们也可以用一个单调队列,入队的是元素的下标索引。这样其实我们能很明显的看出窗口的滑动,只要队首元素的下标<窗口的左边界,那就要把队首移除了,窗口进行了一次滑动;一个很明显的不同,入队的是下标索引

流程

  • 遍历给定数组中的元素,如果队列不为空且当前考察元素大于等于队尾元素,则将队尾元素移除。直到,队列为空或当前考察元素小于新的队尾元素;
  • 当队首元素的下标小于滑动窗口左侧边界left时,表示队首元素已经不再滑动窗口内,因此将其从队首移除。
  • 由于数组下标从0开始,因此当窗口右边界right+1大于等于窗口大小k时,意味着窗口形成。此时,队首元素就是该窗口内的最大值。
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int[] res = new int[nums.length-k+1];
if(nums.length == 0 || nums == null) return new int[]{}; //特例为空;
Deque<Integer> deque = new LinkedList<>();
//right 为窗口右边界;
for(int right = 0; right < nums.length; right++){
//如果队列不为空且当前考察值>队尾元素,将队尾元素移除,直到为空或遇到大的;
while(!deque.isEmpty() && nums[right] > nums[deque.peekLast()]){
deque.removeLast();
}
deque.offerLast(right); //存储下标;
int left = right-k+1; //窗口左侧边界下标;
if(deque.peekFirst() < left){
deque.removeFirst(); //窗口进行了移动,左侧出去;
}
if(right + 1 >= k){
res[left] = nums[deque.peekFirst()]; //这时候窗口形成,开始逐步得到答案;
}
}
return res;
}
}

体会

  • 滑动窗口一共有两种类型:

    • 窗口长度可变:这种类型中长度是可以变化的,一个基本的流程就是,右边界长,然后到达某一个条件(比如窗口内的和达到某个值,窗口中出现了重复的元素),这时候右边界停下来,左边界长,然后跳出这个条件(比如窗口内的和又小于目标值了,比如窗口中又没有重复元素了),这时候右边界再去移动;(我们要处理的始终保证窗口内满足某个条件,例如窗口内的值小于某值,窗口内没有重复的,只要不满足了就去移左边界);
    • 窗口长度固定,比如说固定一个长度的窗口的时候,那右边界长的时候,左边界也得跟着长,维持一个窗口的恒定值;
  • 要始终明白滑动窗口的左右边界是不会出现回退的,两个边界肯定都是朝着一个方向前进的,不会走回头路。

  • 其次要知道滑动窗口其实就是一个队列,右边界移动就是有新元素入队了,左边界移动就是有元素出队了,所以在做题的时候可以想象成一个队列在进行处理,可能会想的更清楚;

参考链接

滑动窗口的最大值

【剑指offer】59 - I. 滑动窗口的最大值的更多相关文章

  1. 剑指 Offer 59 - I. 滑动窗口的最大值 + 双指针 + 双端队列

    剑指 Offer 59 - I. 滑动窗口的最大值 Offer_59_1 题目详情 方法一:暴力方法+双指针 package com.walegarrett.offer; /** * @Author ...

  2. 力扣 - 剑指 Offer 59 - I. 滑动窗口的最大值

    题目 剑指 Offer 59 - I. 滑动窗口的最大值 思路1(单调队列) 使用单调(递减)队列,保持队列中的元素是递减顺序,队列头保存的是当前窗口中最大的元素 首先先模拟建立第一个窗口,同时获取第 ...

  3. 剑指offer 面试题. 滑动窗口的最大值

    题目描述 给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值.例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6 ...

  4. 剑指offer系列38----滑动窗口的最大值(不懂????????????????????????????????????????????????)

    [题目] 给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值.例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6 ...

  5. 剑指 Offer 59 - II. 队列的最大值--滑动窗口的建模+Deque的基本使用(常用方法)

    剑指 Offer 59 - II. 队列的最大值 题目链接 package com.walegarrett; /** * @Author WaleGarrett * @Date 2020/12/3 1 ...

  6. 《剑指offer》面试题59 - I. 滑动窗口的最大值

    问题描述 给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值. 示例: 输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3 输出: [3,3,5,5 ...

  7. 剑指offer-面试题59_1-滑动窗口的最大值-数组

    /* 题目: 链接:https://www.nowcoder.com/questionTerminal/1624bc35a45c42c0bc17d17fa0cba788 来源:牛客网 给定一个数组和滑 ...

  8. 剑指Offer 59. 按之字形顺序打印二叉树 (二叉树)

    题目描述 请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推. 题目地址 https://www.nowco ...

  9. [剑指Offer] 59.按之字形顺序打印二叉树

    题目描述 请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推. [思路]先按层次遍历存入,通过设立标志位,将 ...

随机推荐

  1. 66.QT-线程并发、QTcpServer并发、QThreadPool线程池

    1.线程并发一个程序内部能拥有多个线程并行执行.一个线程的执行可以被认为是一个CPU在执行该程序.当一个程序运行在多线程下,就好像有多个CPU在同时执行该程序.总之,多线程即可以这么理解:多线程是处理 ...

  2. 无向图(邻接表实现)DFS_AND_BFS

    数据结构选择TreeSet的原因:通过自定义的Compare方法,保证了点元素的唯一性,有序性(方便检验): 传入Set和Map中的元素类似于C中的指针操作,即共享地址,改变其中一个中的元素,与之相关 ...

  3. 12.5finally子句

    要点提示:无论异常是否产生,finally子句总是会执行的. 有时候无论异常是否出现或者是否被捕获,都希望执行某些代码.java有一个finally子句,可以用来达到这个目的. 注意:使用finall ...

  4. 自然语言处理(NLP)——简介

    自然语言处理(NLP Natural Language Processing)是一种专业分析人类语言的人工智能.就是在机器语⾔和⼈类语言之间沟通的桥梁,以实现人机交流的目的. 在人工智能出现之前,机器 ...

  5. printf 格式

    1.转换说明符      %a(%A)     浮点数.十六进制数字和p-(P-)记数法(C99)      %c         字符      %d         有符号十进制整数      % ...

  6. 触宝科技基于Apache Hudi的流批一体架构实践

    1. 前言 当前公司的大数据实时链路如下图,数据源是MySQL数据库,然后通过Binlog Query的方式消费或者直接客户端采集到Kafka,最终通过基于Spark/Flink实现的批流一体计算引擎 ...

  7. Docker环境RabbitMq配置SSL

    RabbitMQ要对外提供服务,考虑到安全性,配置SSL进行访问,ssl端口5671,内部仍然使用5672进行访问,两者同时兼容. 安装环境 CentOS 7.5 Docker 1.13.1 Git ...

  8. Linux date 获取时间

    获取当前日期: ubuser@ubuser-OptiPlex-7010:~$ date +%Y_%m_%d2020_12_16 获取当前时间: ubuser@ubuser-OptiPlex-7010: ...

  9. MySQL 卡死的问题

    1. 执行show full processlist观察state和info两列,查看有哪些线程在运行. 2.使用kill命令+对应线程前面id杀死卡死的线程. 其他的方式: -- 查询是否锁表 sh ...

  10. php弱类型比较

    前言:今天XCTF题目中出现了弱类型比较,特别过来记录一下, 0x01: == 是弱类型比较,两个不同类型比较时,会自动转换成相同类型后再比较值 ===是强比较,需要比较值和类型 0x02: 看下图案 ...