单调队列主要用于降低时间复杂度,使得在O(1)的时间内可以得到栈内最大/小值。
单调队列,顾名思义,就是一个单调递减/递增的队列
力扣上的对应题目有剑指Offer 30. 包含 min 函数的栈,剑指 Offer 59 - I. 滑动窗口的最大值,队列的最大值
以滑动窗口的最大值为例
输入: 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(nk)
为了使时间复杂度降为O(1),可以构建一个单调队列作为辅助
- 使用双指针来模拟滑动窗口(注意这个滑动窗口并不是固定不变的大小,也可以变大或变小,因此可以模拟很多情况,应用到其他题目)
- 构建一个单调队列用来存储当前的窗口的最大值,保持队列一直都是单调递减的,这样每次取最大值就是取队列第一个,时间复杂度为O(1).
- 维护单调队列,就需要在插入和删除元素(窗口滑动)时,做一些改变。
插入(窗口滑动新增的数)如果单调队列中没有元素,则该元素就是最大的,向单调队列中插入该元素
如果单调队列中有元素,则弹出所有比它小的元素再将其入队尾
删除(窗口滑掉的数)如果删掉的数和单调队列中第一个元素相同,则同时弹出单调队列第一个元素
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
if(nums.length == 0 || k == 0) return new int[0];
Deque<Integer> deque = new LinkedList<>();
int[] res = new int[nums.length - k + 1];
// 未形成窗口
for(int i = 0; i < k; i++) {
while(!deque.isEmpty() && deque.peekLast() < nums[i])
deque.removeLast();
deque.addLast(nums[i]);
}
res[0] = deque.peekFirst();
// 形成窗口后
for(int i = k; i < nums.length; i++) {
if(deque.peekFirst() == nums[i - k])
deque.removeFirst();
while(!deque.isEmpty() && deque.peekLast() < nums[i])
deque.removeLast();
deque.addLast(nums[i]);
res[i - k + 1] = deque.peekFirst();
}
return res;
}
}
注意在队列的最大值那里,判断两个list取出的元素是否相同时,一定要进行类型转换,两个Integer对象无法直接用==判断是否相等
list.getFirst()==(int)tmp.getFirst()