【剑指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.按之字形顺序打印二叉树
题目描述 请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推. [思路]先按层次遍历存入,通过设立标志位,将 ...
随机推荐
- 基于Yarp实现内网http穿透
Yarp介绍 YARP是微软开源的用来代理服务器的反向代理组件,可实现的功能类似于nginx. 基于YARP,开发者可以非常快速的开发一个性能不错的小nginx,用于代理http(s)请求到上游的ht ...
- git分支案例
- 『心善渊』Selenium3.0基础 — 28、unittest中测试套件的使用
目录 1.测试套件的作用 2.使用测试套件 (1)入门示例 (2)根据不同的条件加载测试用例(了解) (3)常用方式(推荐) 1.测试套件的作用 在我们实际工作,使用unittest框架会有两个问题: ...
- ESP32 ADF windows开发环境搭建 适配ADF到ESP32A1S(转)
搭建ESP32A1S的ADF开发环境 一,获取IDF和IDF-TOOL adf是乐鑫的音频开发框架,里面有许多乐鑫的音频开发API,同时ADF是基于IDF的.这一部分可以按照官网的教程一步一步来.官网 ...
- 测试基础(三) Jmeter安装
前言 JMeter是Apache组织开发的基于Java的压力测试工具,用于对软件做压力测试. 进入Jmeter官网:https://jmeter.apache.org/,进行Jmeter压缩包的下载. ...
- WORD加目录
1.WORD2010样式右下角小按钮,打开"样式"对话框,打开"管理样式"按钮 2.选择"标题1"--显示--上移(1) 3.依次再选择&q ...
- ERROR 1045 (28000): Access denied for user ‘root‘@‘localhost‘ (using password: NO)解决办法
问题:ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO) 很久没用这台电脑的mysql ...
- react应用(基于react脚手架)
使用create-react-app创建react应用 react脚手架 1) xxx脚手架: 用来帮助程序员快速创建一个基于xxx库的模板项目 a. 包含了所有需要的配置 b. 指定好了所有的依赖 ...
- vuejs第一集之:vuejs了解
1,了解到前后端分离2,连接到vuejs3,搜集书籍: Vuejs前端开发基础与项目实战 (https://detail.tmall.com/item.htm?spm=a230r.1.14.107.6 ...
- Unittest方法 -- 测试固件(TestFixture)
前置和后置 1.setUp:在写测试用例的时候,每次操作其实都是基于打开浏览器输入对应网址这些操作,这个就是执行用例的前置条件.2.tearDown:执行完用例后,为了不影响下一次用例的执行,一般有个 ...