【剑指offer】59 - I. 滑动窗口的最大值
剑指 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. 滑动窗口的最大值的更多相关文章
- 剑指 Offer 59 - I. 滑动窗口的最大值 + 双指针 + 双端队列
剑指 Offer 59 - I. 滑动窗口的最大值 Offer_59_1 题目详情 方法一:暴力方法+双指针 package com.walegarrett.offer; /** * @Author ...
- 力扣 - 剑指 Offer 59 - I. 滑动窗口的最大值
题目 剑指 Offer 59 - I. 滑动窗口的最大值 思路1(单调队列) 使用单调(递减)队列,保持队列中的元素是递减顺序,队列头保存的是当前窗口中最大的元素 首先先模拟建立第一个窗口,同时获取第 ...
- 剑指offer 面试题. 滑动窗口的最大值
题目描述 给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值.例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6 ...
- 剑指offer系列38----滑动窗口的最大值(不懂????????????????????????????????????????????????)
[题目] 给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值.例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6 ...
- 剑指 Offer 59 - II. 队列的最大值--滑动窗口的建模+Deque的基本使用(常用方法)
剑指 Offer 59 - II. 队列的最大值 题目链接 package com.walegarrett; /** * @Author WaleGarrett * @Date 2020/12/3 1 ...
- 《剑指offer》面试题59 - I. 滑动窗口的最大值
问题描述 给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值. 示例: 输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3 输出: [3,3,5,5 ...
- 剑指offer-面试题59_1-滑动窗口的最大值-数组
/* 题目: 链接:https://www.nowcoder.com/questionTerminal/1624bc35a45c42c0bc17d17fa0cba788 来源:牛客网 给定一个数组和滑 ...
- 剑指Offer 59. 按之字形顺序打印二叉树 (二叉树)
题目描述 请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推. 题目地址 https://www.nowco ...
- [剑指Offer] 59.按之字形顺序打印二叉树
题目描述 请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推. [思路]先按层次遍历存入,通过设立标志位,将 ...
随机推荐
- C++ 11 关键字
1.auto 我现在用auto,基本是在变量定义时根据初始化表达式自动推断该变量的类型. 另外与关键字 decltype 连用,在声明或定义函数时作为函数返回值的占位符. auto不能用来声明函数的返 ...
- 04 jumpserver资产管理
4.资产管理: (1)管理用户: 管理用户是资产(被控服务器)上的 root,或拥有 NOPASSWD: ALL sudo 权限的用户, JumpServer 使用该用户来 `推送系统用户`.`获取资 ...
- 揭开Docker的面纱
开新坑了,开始挖坑Docker了,兄弟们.为什么需要Docker呢?Docker是什么?这里开始揭开Docker的面纱. 一.为什么需要Docker 可能每个开发人员都有一种困扰,软件开发完之后部署项 ...
- 【知识点】C++的日志框架
常用的 C++ 日志框架 一.日志框架 一个经过专门设计的实用程序,用于规范应用程序的日志记录过程,就是日志框架. 日志框架可以自己编写(需要一定的能力哦),也可以由第三方(例如:log4cplu ...
- 【重学Java】Stream流
Stream流 体验Stream流[理解] 案例需求 按照下面的要求完成集合的创建和遍历 创建一个集合,存储多个字符串元素 把集合中所有以"张"开头的元素存储到一个新的集合 把&q ...
- 计算机基础 | 文档神器docsify安装及基本使用
为啥要用docsify? 神器Docsify让你的文档变得美观,配合typora,从此爱上看文档,各种优点:小巧.快速.美观.方便.快捷.上手快,可以浏览如下优秀案例 ve-charts pyecha ...
- VMware Esxi开通SSH功能
ESXi 5.5是直接安装在物理主机上的一个虚拟机系统,本质上是一个Linux系统.平时可以通过VMware Client端或者VMware vCenter进行管理,但对于一些特殊的VMware命令或 ...
- QT从入门到入土(三)——文件的读写操作
引言 文件的读写是很多应用程序具有的功能,甚至某些应用程序就是围绕着某一种格式文件的处 理而开发的,所以文件读写是应用程序开发的一个基本功能. Qt 提供了两种读写纯文本文件的基本方法: 用 QFi ...
- C语言:标识符 关键字 保留字 表达式 语句
标识符(Identifier)标识符就是程序员自己起的名字,符号常量(宏).变量名.函数名.宏名.结构体名等,它们都是标识符.遵守规范:C语言规定,标识符只能由字母(A~Z, a~z).数字(0~9) ...
- Java基础00-多态19
1. 多态 多态 1.1 多态概述 代码示例: 动物类: public class Animal { public void eat(){ System.out.println("动物吃东西 ...