Leetcode.239. 滑动窗口最大值---暴力/优化暴力/单调队列
作者:互联网
239. 滑动窗口最大值
给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回滑动窗口中的最大值。
示例 1:
输入: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
示例 2:
输入:nums = [1], k = 1
输出:[1]
示例 3:
输入:nums = [1,-1], k = 1
输出:[1,-1]
示例 4:
输入:nums = [9,11], k = 2
输出:[11]
示例 5:
输入:nums = [4,-2], k = 2
输出:[4]
提示:
1 <= nums.length <= 105
-104 <= nums[i] <= 104
1 <= k <= nums.length
题解:
方法一:暴力
- 我们直接维护一个
滑动窗口
,接着按照要求进行pop
与push
的操作即可,但是由于每次都要求一次滑窗里的max
,所以此法很耗费时间。
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
List list = new ArrayList();
int[] res = new int[nums.length];
int index = 0;
for(int i=0;i<nums.length;i++){
if(list.size()<k){
list.add(nums[i]);
}
else{
res[index++] = cal(list);
list.remove((int)0);
list.add(nums[i]);
}
}
res[index++] = cal(list);
int[] temp = new int[index];
for(int i=0;i<index;i++){
temp[i] = res[i];
}
return temp;
}
public int cal(List list){
int res = -20000;
for(int i=0;i<list.size();i++){
res = Math.max(res , (int)(list.get((int)i)));
}
return res;
}
}
方法二:优化暴力
- 在方法一的基础上,增加存储滑窗
max
值的设计,即在滑窗移动后求新的max
时不是直接计算新的max
,而是判断一下原先的max
是否被移出滑窗
,如果没有则继续用这个max
,接着再判断一下要加进来的元素和原max
之间的大小关系,如果要进滑窗的元素直接大于原max
,则原max
直接禅位
即可。其实和下面法三有异曲同工之妙,在于维护滑窗
的同时,维护了一个仅存储滑窗max值及其所处滑窗位置的res数组
。 - 需要注意的是在
cal
函数中之所以要传入flag
这个布尔类型数组,是因为值传递的话修改的只是形参,没有真正的修改值,所以这里使用数组进而使其变成引用传递
,类似于c++中使用指针。
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
List list = new ArrayList();
int[] res = new int[nums.length];
int index = 0;
int rep = 0;
int[] ini = new int[2];
boolean[] flag = new boolean[1];
flag[0] = false;
for(int i=0;i<nums.length;i++){
if(list.size()<k){
list.add(nums[i]);
}
else{
ini = cal(list,flag,ini);
res[index++] = ini[0];
if(ini[1]==0){
flag[0] = false;
}
if(nums[i]>ini[0]){
ini[0] = nums[i];
ini[1] = k-1;
}
list.remove((int)0);
ini[1]--;
list.add(nums[i]);
}
}
ini = cal(list,flag,ini);
res[index++] = ini[0];
int[] temp = new int[index];
for(int i=0;i<index;i++){
temp[i] = res[i];
}
return temp;
}
public int[] cal(List list,boolean[] flag,int[] res){
if(flag[0]){
return res;
}
res[0] = -20000;
for(int i=0;i<list.size();i++){
int temp = res[0];
res[0] = Math.max(res[0] , (int)(list.get((int)i)));
if(res[0]!=temp){
res[1] = i;
}
}
flag[0] = !flag[0];
return res;
}
}
方法三:单调队列
我们设计一个单调队列
类作为我们维护的滑窗,注意的是其只是维护一个单调递减的数列
,这样的话我们在取队列里的最大值时直接拿头部元素即可。
- 另外这个队列在添加滑窗尾部元素时不只是简单的添加元素,而是先进行判断,判断要加入的元素是否满足原队列的递减关系,如果满足直接添加即可;如果不满足,则判断一下其和队尾元素的关系,如果其大于队尾元素则将队尾元素干掉,自己加入进去,接着继续和队尾进行判断即可。
- 此队列在删除滑窗头部元素时,也不是直接删除队头元素,因为此队列的顺序不是严格按照滑窗内元素的顺序来的,因此我们不可直接删除队头,而是判断一下要删除的元素是否是队头元素,是则真正的删除,不是则无需删除。
- 通过设计这样一个
单调队列
,我们发现问题其实就迎刃而解,并且由于我们每次移动后由队列直接告诉我们最大值,我们无需自己去通过遍历滑窗来得到最大值,因此此法不会超时。
或许你会疑问此单调队列的pop
和push
的设计原理:
- 为什么
push
的时候当遇到要添加的元素大于队尾元素时,要删除队尾元素把自己加进去?
因为之所以维护一个
单调递减队列
,就是为了保存“某个滑窗的可能最大值”
,首先队头元素一定是此时滑窗的最大值,接着如果滑窗移动一次后,队头元素可能还是此滑窗的最大值,但也有可能不是,即其刚好是被删除的情况。假如其不是,那么新队列的队头元素就是此滑窗的最大值。
而这个新队头的来源有两个,即要么为原先的队列二号位,要么是新加入滑窗的元素。即使新加入的元素很小,他也是有可能成为
“某个滑窗的可能最大值”
,因此不能删除。而当新加入的元素处于单调队列的区间内,之所以要实行干掉操作,是因为单调队列的长度是小于等于滑窗的长度,假设其不干掉小于他的元素,当滑窗移动到他应为滑窗最大值(在后面没别的元素干他的情况下)时,我们会发现此时的他不在队头,而且我们发现保留的前面的那些元素并没起到任何作用,因此前面那些元素我们需要干掉,来保证形成一个纯净的有用的单调队列
。
这里我们可以将单调队列
理解为这么一个过程:
单调队列
可以理解为在修仙界的一个宗门,宗门的创始人必定是冷血无情,却又实力雄厚的,因此成为了老大,后来他变的仁慈起来,广收弟子。当弟子们十分强大时,又会冷血无情的将比自己弱的人全部干掉,包括老大,干掉之后又会恢复仁慈的性格,收自己的弟子,自己成为老大。当然老大也会自己老死,那么将由老二接管。单调队列
便是这样一个如此循环往复的过程。
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int[] res = new int[nums.length];
int index = 0;
Myquene myquene = new Myquene();
for(int i=0;i<k;i++){
myquene.push(nums[i]);
}
for(int i=k;i<nums.length;i++){
res[index++] = myquene.front();
myquene.push(nums[i]);
myquene.pop(nums[i-k]);
}
res[index++] = myquene.front();
int[] temp = new int[index];
for(int i=0;i<index;i++){
temp[i] = res[i];
}
return temp;
}
}
class Myquene{
List<Integer> list = new ArrayList<>();
int front(){
return list.get((int)0);
}
void pop(int num){
if(list.size()!=0 && num==front()){
list.remove((int)0);
}
}
void push(int num){
while(list.size()!=0 && num>list.get(list.size()-1)){
list.remove((int)(list.size()-1));
}
list.add(num);
return;
}
}
标签:暴力,滑窗,nums,int,res,list,---,队列,Leetcode.239 来源: https://blog.csdn.net/xiangguang_fight/article/details/121473422