单调队列
对应leetcoe题目 239. 滑动窗口最大值
其实在JDK API中是没有实现单调队列这个东西的,但是单调队列的特性可以帮助我们很好的解决这道题目。
定义
单调队列是一种特殊的队列,它可以在 O(1) 的时间内实现以下操作:
-
在队列尾部添加一个元素;
-
删除队列头部的元素;
-
获取队列中的最大值或最小值。
单调队列通常用于解决一些滑动窗口问题,例如在一个数组中,找到每个长度为 k 的子数组中的最大值或最小值。(完美适用本题)
单调队列的实现比较简单,通常使用双端队列来实现。具体来说,可以使用一个双端队列 deque 来存储数组中的元素,deque 中的元素按照单调递增(单调递减)的顺序排列,队头为最大值(最小值)。
-
当添加一个新元素时,首先将队列中比它小的元素都删除,保持队列的单调性;然后将新元素加入队列尾部。
-
当删除一个元素时,判断队头元素是否等于被删除的元素,如果是则将其从队列头部删除。
这样,单调队列可以保持队列的单调性,并在 O(1) 的时间内实现插入、删除和获取最大值或最小值的操作。
例如:
nums = [1,3,-1,-3,5,3,6,7], k = 3
(滑动窗口大小为3)
因为我们要找窗口内的最大值,所以要将最大值放在队头的位置,故队列为单调递减队列
-
滑动窗口在开始位置时:先将前三个数入队
-
1入队:que = 1
-
3入队:que = 3(因为是单调递减队列,所以会将队尾所有小于3的值都从队尾弹出
-
-1入队:que = 3,-1
-
-
将前三个的最大值(队头元素,放入结果集):
res[idx] = que.pollFirst();
-
开始移动滑动窗口
-
窗口左侧(
nums[0]=1
)右移一位:判断nums[0]
和当前队头元素(3)是否相等。相等则需要队头元素出队,然后窗口左侧右移;如果不相等,窗口左侧直接右移 -
窗口右侧(
nums[k-1]=-1
)右移一位:循环判断nums[k]
是否大于队列队尾元素,大于则队尾元素弹出,直到不大于为止。再把nums[k]
入队。 -
此时的滑动窗口为:3,-1,-3。队列里的值为:3,-1,-3.重复此动作,一直到遍历完整个数组。
-
代码
class Solution { public int[] maxSlidingWindow(int[] nums, int k) { int len = nums.length; //存放结果 int[] res = new int[len-k+1]; //把双端队列当成消息队列用,改造一下 MyQueue que = new MyQueue(); //存res的下标 int idx = 0; //先将前k个元素入队 for (int i = 0; i < k; i++) { que.push(nums[i]); } res[idx++] = que.getMax(); for (int i = k; i < len; i++) { int right = nums[i]; int left = nums[i-k]; //滑动窗口左侧弹出数据 que.pop(left); //滑动窗口右侧压入数据 que.push(right); //找出窗口内最大值进入res res[idx++] = que.getMax(); } return res; } } //定义单调队列 class MyQueue{ Deque<Integer> que = new LinkedList<>(); //如果当前队尾元素小于要push的元素,则与要弹栈 public void push(int val){ while (!que.isEmpty() && que.peekLast() < val) { que.pollLast(); } que.offer(val); } //每次弹出的时候要比较弹出的值和队列出口的值是否相等 public void pop(int val){ if(!que.isEmpty() && que.peekFirst() == val) { que.pollFirst(); } } public int getMax(){ return que.peekFirst(); } }
参考资料: