题目
给定一个数组 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
提示:
你可以假设 k 总是有效的,在输入数组不为空的情况下,1 ≤ k ≤ 输入数组的大小。
思路-单调非递增+滑动窗口
遍历nums数组,每轮保证单调队列deque:
- deque内仅包含窗口内的元素
- deque内的元素非严格递减
- 队首元素就是窗口的最大元素
- nums为null或0,返回空数组(new int[0])
- 定义一个栈和一个结果数组(大小nums.length-k+1)
- 构造单调队列1的首个窗口(单调非递增)
1. 队列不为空时,当前值与队列尾部值比较,如果大于,删除队列尾部值, 一直循环删除到队列中的值都大于当前值,或者删到队列为空
2.执行完上面的循环后,队列中要么为空,要么值都比当前值大- 窗口形成后,就需要把队列首位添加到数组中,因为下面的循环是直接跳过这一步的,所以需要我们直接添加
- 循环依次构造窗口
- 构造下一个窗口需要删除上一个窗口的首元素如果首位等于nums[i-k],那么说明此时nums[i-k]就是上一个窗口的首元素值,而下一个窗口不需要它,需要删除
- 队列不为空时,当前值与队列尾部值比较,如果大于,删除队列尾部值, 一直循环删除到队列中的值都大于当前值,或者删到队列为空
- 执行完上面的循环后,队列中要么为空,要么值都比当前值大
- 单调非递增队首元素就是最大值
- 返回结果集
代码
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
if(nums==null||nums.length==0){
return new int[0];
}
//单调非递增栈
Deque<Integer> stack=new LinkedList<>();
//结果集大小nums.length-k+1
int[] res=new int[nums.length-k+1];
//未成形窗口
for(int i=0;i<k;i++){
//队列不为空时,当前值与队列尾部值比较,如果大于,删除队列尾部值
//一直循环删除到队列中的值都大于当前值,或者删到队列为空
while(!stack.isEmpty()&&stack.peekLast()<nums[i]){
stack.removeLast();
}
//执行完上面的循环后,队列中要么为空,要么值都比当前值大,然后就把当前值添加到队列中
stack.addLast(nums[i]);
}
//因为窗口形成后,就需要把队列首位添加到数组中,而下面的循环是直接跳过这一步的,所以需要我们直接添加
res[0]=stack.peekFirst();
//形成窗口
for(int i=k;i<nums.length;i++){
//构造下一个窗口需要删除上一个窗口的首元素
//i-k是已经在区间外了,如果首位等于nums[i-k],那么说明此时首位值是上一个窗口的值,需要删除
if(stack.peekFirst()==nums[i-k]){
stack.removeFirst();
}
//队列不为空时,当前值与队列尾部值比较,如果大于,删除队列尾部值
//一直循环删除到队列中的值都大于当前值,或者删到队列为空
while(!stack.isEmpty()&&stack.peekLast()<nums[i]){
stack.removeLast();
}
//执行完上面的循环后,队列中要么为空,要么值都比当前值大,然后就把当前值添加到队列中
stack.addLast(nums[i]);
//单调非递增栈首元素就是最大值
res[i-k+1]=stack.peekFirst();
}
return res;
}
}
优化版
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 j = 0, i = 1 - k; j < nums.length; i++, j++) {
// 删除 deque 中对应的 nums[i-1]
if(i > 0 && deque.peekFirst() == nums[i - 1])
deque.removeFirst();
// 保持 deque 递减
while(!deque.isEmpty() && deque.peekLast() < nums[j])
deque.removeLast();
deque.addLast(nums[j]);
// 记录窗口最大值
if(i >= 0)
res[i] = deque.peekFirst();
}
return res;
}
}
复杂度
时间复杂度O(n): 其中 nn 为数组 numsnums 长度;线性遍历 numsnums 占用 O(n)O(n) ;每个元素最多仅入队和出队一次,因此单调队列 dequedeque 占用 O(2n)O(2n) 。
空间复杂度 O(k) : 双端队列 dequedeque 中最多同时存储 k 个元素(即窗口大小)。