题目
给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回 滑动窗口中的最大值 。
提示:
1 <= nums.length <= 10^5
-10^4 <= nums[i] <= 10 ^4
1 <= k <= nums.length
思路
如果暴力做法,就是维护一个长度为k的数组,没移动时先遍历该数组,把最大值找出来。然后每移动一次,如果右边新加入的数>原最大值,则更新最大值。如果左边移走的数就是最大值,那么需要遍历此时移动后的数组,找出最大值。这个找出最大值的过程为O(k)。移动整体窗口的过程是O(n),合起来就是O(nk),会超出时间限制。因为移动整体窗口的过程是省略不了的,我们只能考虑如何将找出最大值的O(k)降为O(1)。
我们观察滑动窗口,如果此时滑动窗口中只有两个下标i和j,且i<j,nums[i]>=nums[j],那么只要i还在窗口内,那么j也一定还在窗口内。且nums[i]一定是窗口中的最大值,我们便不用再去遍历整个窗口了。如果我们以这个性质为出发点,维护一个严格单调递减的,下标从小到大的队列。即队列里存储的是下标,但是值是严格单调递减的。那么就相当于队列里存储的是原数组第一大元素的下标,原数组第二大元素的下标,原数组第三大元素的下标……。
当向右滑动,遍历到一个新元素now时,我们
- 首先需要判断now是否大于队列末尾的第k大元素klast。
- 如果now>klast,那么意味着队列的元素,谁是第几大需要重新定义,于是只要队列不为空,我们便将now与队列末尾的元素last一个个比较,如果now大于last,就将last踢出队列,直到遇到一个比now大的元素,再将now插入到队列末尾(因为那些被踢掉的last永远不可能是答案,队列没有维护它们的必要)。
- 如果now<klast,那么直接将now插入到队列末尾即可。
- 其次,我们再判断向右滑动时是否会导致队首,即原数组第一大元素滑出队列,因为队列中存储的是在原数组的下标,那么我们只需要判断这个队首元素是否>i-k即可。
java代码
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int len = nums.length;
Deque<Integer> deque = new LinkedList<>();
int[] ans = new int[len-k+1];
for(int i=0;i<k;i++){
while(!deque.isEmpty()&&nums[i]>=nums[deque.peekLast()]){
deque.pollLast();
}
deque.offerLast(i);
}
ans[0]=nums[deque.peekFirst()];
for(int i=k;i<len;i++){
while(!deque.isEmpty()&&nums[i]>=nums[deque.peekLast()]){
deque.pollLast();
}
deque.offerLast(i);
if(deque.peekFirst()<=i-k){
deque.pollFirst();
}
ans[i-k+1]=nums[deque.peekFirst()];
}
return ans;
}
}
效率分析
28ms,击败80.23%使用 Java 的用户。没必要再优化了。