剑指 Offer 59 - I. 滑动窗口的最大值
给定一个数组 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 ≤ 输入数组的大小。
注意:
本题与主站 239 题相同:https://leetcode-cn.com/problems/sliding-window-maximum/
解题思路
最暴力的做法当然是模拟
滑动窗口的过程,扫描每个滑动窗口中的所有数字并找出其中的最大值,这样的复杂度是$O(kn)$的。显然题目出在这里肯定不是要我们用暴力解法。
窗口向右滑动的过程实际上就是将处于窗口的第一个数字删除,同时在窗口的末尾添加一个新的数字,这就可以用双向队列
来模拟,每次把头部的数字弹出,再把新的数字添加到队列尾部,然后找队列中最大的元素即可。
为了使用$O(1)$的时间就能找到最大的元素,我们并不把滑动窗口的每个数值都存入队列,而是只把有可能成为滑动窗口最大值的数值存入队列。
1. 当前窗口中元素大于k
个时,说明队头的元素已经从窗口滑出,需要从队列头部出队
。
2. 队尾已有的数字小于等于当前要存入的数字,那么队尾这些数字已经不可能是滑动窗口的最大值,因为待存入的数字比它们大,还要比他们晚滑出窗口。所以队尾这些元素都要从队尾出队
。
3. 当前面元素都滑出当前窗口时,当前元素可能会是后面窗口的最大值,所以当前元素无论如何都要入队。
因此我们维护一个双向队列,队列放的是元素的下标。我们维护该双端队列的队头是整个队列的最大元素所在下标
。
时间复杂度: 每个元素最多入队出队一次,复杂度为$O(n)$
Java代码
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
if(nums == null || nums.length == 0) return new int[0];
Deque<Integer> deque = new LinkedList<>();
int[] res = new int[nums.length - k + 1];
int index = 0;
for(int i = 0;i < nums.length;i++){
if(!deque.isEmpty() && i - deque.peekFirst() + 1 > k){//当前窗口中元素大于k个
deque.removeFirst();//队头出队
}
//当前元素大于队尾元素,则队尾这些元素只要当前元素在,就不可能是窗口中的最大值,可以直接拿走了
while(!deque.isEmpty() && nums[i] >= nums[deque.peekLast()]){
deque.removeLast();
}
//当前面元素都滑出当前窗口时,当前元素可能会是后面窗口的最大值,所以当前元素无论如何都要入队
//注意队列中存的是元素在数组中的下标,方便判断窗口中当前元素有多少个了
deque.addLast(i);
if(i + 1 >= k){//窗口中元素有k个时
res[index++] = nums[deque.peekFirst()];
}
}
return res;
}
}