题目描述:
给你一个整数数组 nums
,有一个大小为 k
的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k
个数字。滑动窗口每次只向右移动一位。
返回 滑动窗口中的最大值 。
示例 1:
输入: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
示例 2:
输入:nums = [1], k = 1 输出:[1]
提示:
1 <= nums.length <= 105
-104 <= nums[i] <= 104
1 <= k <= nums.length
题解:这是一道不看官方题解完全想不出来的题,思路如下:如果按照暴力方法,会产生很多无用的判断,比如现在窗口(1,2,4),2,1 最大值是4,并且在之后移动到的两个窗口中最大值还是4,使用暴力方法的时候,就需要分别对(1,2,4) (2,4,2) (4,2,1)进行查找最大值判断,产生了大量的重复计算,因此考虑优化这部分。实现思路是:“头尾尾头”的四步骤:
1.头:
清理超期元素,如果遍历到i位置,说明i-k位置的元素需要被清理掉,从队列中remove掉
2.尾:
每次滑动窗口的时候维护一个保存可能成为某个窗口最大值的数组元素下标队列。比如现在数组是1,2,4,5,x 滑动窗口大小k=3 如果在遍历到某一次的时候发现下一个要添加到窗口中的元素是下标为3的元素5,我们肉眼可以观察到,只要5被圈入一个滑动窗口(2,4,5) (4,5,x)这两个窗口的最大值就永远不可能再是 2和4,因此当5要进入窗口的时候,在队列中删除小于5的元素对应的下标,因为新圈入的元素值比旧窗口元素值大,那么新窗口的最大值就一定比旧窗口元素值大。
3.尾:
新元素入队列,将新元素的下标添加进队列,现在队列维护的就是后续滑动窗口的时候可能成为窗口最大值的元素下标,因为不可能在滑动窗口的时候 成为窗口最大值的元素已经在新元素入队列之前,通过步骤2让新元素和当前队列中的每一个元素比较过了,将小于新元素的旧元素全部从队列中删除了,换句话说,就是将新元素入队列之后,不可能成为最大值的元素全部删除了,每一次将一个元素入队列之前,都将小于该元素的值移除队列,就可以保证队列的首元素在没过期之前一直是需要求的最大值,减少了比较次数。
4.头:
将最大值记录在结果数组中
代码实现:
public static int[] maxSlidingWindow(int[] nums, int k) {
//特判
if(k==0||nums.length==0){
return new int[]{};
}
//双端队列维护一个单调递减的队列,存放的是数组元素下标
Deque<Integer> deque = new LinkedList<>();
//结果
int resIndex = 0;
//初始化结果数组
int[] res = new int[nums.length-k+1];
for (int i = 0; i < nums.length; i++) {
//1.头:清理超期元素
//新一个元素加入窗口的时候需要判断这个下标为i的元素是否会把
// 下标为i-k的元素挤出去(如果下标为i-k的元素在的话)
if(!deque.isEmpty()&&deque.peek()==i-k){
deque.remove();
}
//2.尾:维护单调递减队列(添加如队列一个元素之前,先判断该元素是
// 否比现在队列中的全部未过期的元素值都大,如果都大,那么只要当前位置元素没有过期之前
// 遍历到的所有窗口,最大值都是现在遍历到的该值)
while(!deque.isEmpty()&&nums[i]>=nums[deque.peekLast()]){
deque.removeLast();
}
//3.尾:新元素入队列
deque.addLast(i);
//4.头:保存当前窗口的最大值到数组
if(i>=k-1){
res[resIndex++]=nums[deque.peekFirst()];
}
}
return res;
}