单调队列详解!基于 LeetCode 239. 滑动窗口最大值
属于是单调队列最经典的例题了
单调队列
前置知识:如果事先有单调栈的理论基础就ok了
概念
满足队列内单调递增或单调递减的双端队列
应用时刻
这个数据结构大多应用在求滑动窗口区间内最大最小值的时候格外好用。
如何理解
网上大多数文章都喜欢用acm校队的例子来解释,很通俗啊。
假设每个人的算法能力可以用战力值表示,并在此例子中,每个人的战力值恒定。
一个学校的acm校队一共有大学四个年级的学生(滑动窗口长度为4),四个年级的新生凭借自己的战力值去争抢那唯一的一个出赛机会。
这个学校的第一个新生到了,他当之无愧的坐向了队长宝座,当前的单调队列如下(队头为队长)
<2>
一年过去了,来了一个战力值为1的新生,原先的大一变成了大二
虽然这个时候1并不是区间最大值,但是不代表以后不是,熬死老的自己就是老大了,等到战力值为2的毕业了(也就是不在滑动窗口的范围了),战力值1还是有可能成为队长的,所以还是把1加入队列,此刻将1填入单调队列 <2,1> 。
又一年过去了,这一年来了个大佬新生,他的战力值为3.
那完蛋了,现在这个战力值为2和1的人再怎么熬也熬不到自己成为队长的时候了,因为这个战力值为3的比自己小啊!(3在滑动窗口的头端)。那么这个时候该如何变化队列呢。
首先弹出队尾元素与其(3)比较,如果小于,则弹出队列,直到队列为空或队尾元素比他大,这个时候把这个3插入队列就好了
那么现在的单调队列就变为 <3> 因为2和1这辈子都没希望了呀,那留他在队列里有何用。
再举一个例子
这个时候的单调队列为 <8,6,4> ,一年过去了,来了一个战力值为7的学生,因为这个时候,战力值为8的学生毕业了(滑出窗口了),所以队头自动弹出,然后将7装入队列,还是上述过程,弹出队尾元素比较,直到队列为空或队尾元素比他大,插入。
所以现在队列变为 <7> ,可怜死了,原本终于熬走一个8的,没想到又来了一个超级新生,直接被队列抛弃了。
因为在整个过程中,我们的队列始终处于单调,所以就叫单调队列。
结合例题
题目详情
给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回 滑动窗口中的最大值 。
根据上述理论不难给出代码
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
//ans数组用作答案返回
int[] ans = new int[nums.length-k+1];
//a作为ans数组的迭代数
int a = 0;
//定义双端队列,注意此刻,为了判断队首元素是否滑出窗口,队列中存入的是nums的下标值
Deque<Integer> queue = new LinkedList<>();
//遍历每个元素
for(int i = 0;i<nums.length;i++){
//当队列不为空并且队尾元素比他小
while(queue.size()!=0&&nums[queue.getLast()]<nums[i]){
//弹出队尾
queue.pollLast();
}
//这个时候加入队尾,因为比他小的元素已经被弹出去了
queue.add(i);
//检查队头元素,如果滑出窗口,则弹出(这个时候存下标的意义就体现出来了)
while(queue.getFirst()<=i-k){
queue.pollFirst();
}
//当遍历的元素个数满足窗口大小的时候,才记录答案
if(i>=k-1){
ans[a++] = nums[queue.getFirst()];
}
}
return ans;
}
}
ps:算法小白,写博客一方面就是为了记录自己的学习过程,加深理解,代码写的有点丑,轻骂