题目描述:
给定一个数组 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
优先队列
官解第一种方法使用的优先队列,这个方法我觉得很妙,于是补充一下细节,加深对题目的理解。
文字描述如下:
创建一个优先队列,不断的向该优先队列中加入数组中的元素。每加入一个元素,视为滑动窗口向右移动一格(此时滑动窗口右指针为i),每次加入元素对该优先队列进行校核。
校核方式为:查看元素中堆顶元素是否属于滑动窗口的范围内,查看的方法为比较堆顶元素下标是否大于滑动窗口左指针下标(i-k)。如果堆顶元素在范围内,无事发生,继续执行下一步指令;如果堆顶元素不在滑动窗口范围内,则弹出该元素,重复校核新堆顶元素,直至堆顶元素满足校核要求。
校核完毕后,将堆顶元素peek一下,放入创建好的数组中即可。
图片描述如下:
代码如下:
public int[] maxSlidingWindow(int[] nums, int k) {
//使用优先队列解题
//int[num,index]
PriorityQueue<int[]> queue = new PriorityQueue<>(new Comparator<int[]>() {
@Override
public int compare(int[] o1, int[] o2) {
//比较数值大小,数值大的放前面;如果数值大小相同,比较下标大小,下标大的放前面
if(o1[0] - o2[0] != 0){
return o2[0] - o1[0];//大值在前面,因此o1如果是大值,返回结果要得到负数才可以在前面,因此此处返回时o2-o1
}else return o2[1] - o1[1];
}
});
int cnt = 0;
int[] tar = new int[nums.length - k + 1];
for (int i = 0; i < k; i++) {
queue.offer(new int[]{nums[i],i});
}
int[] peek = queue.peek();
tar[0] = peek[0];
for (int i = k; i < nums.length; i++) {
//窗口往前移动一格,i为右指针位置
queue.offer(new int[]{nums[i],i});
//查看堆顶元素是否在窗口中,i-k为左指针的位置(这步很妙)
while(queue.peek()[1] <= i - k){
queue.poll();
}
cnt++;
tar[cnt] = queue.peek()[0];
}
return tar;
}
单调队列
除了优先队列,官解还提供了使用单调队列的解法,单调队列很符合本题的要求。
在理解什么是单调队列前,分析一下题目要求:
题目要求是保存每一个滑动窗口内的最大值,我们可以创建一个容器去存储滑动窗口内的元素,不过根据题目要求,我们只要窗口内的最大值就行,即当滑动后出现新的窗口时,只需要保证最大值在容器内存在就可以了,其他数值是否存在不重要。
而且根据这题分析,还有个特性,就当存在两个数i和j,i<j,且nums[i]<nums[j]时,这个下标为i的数就没什么存在的必要了,因为最大值因为有j的存在,永远有轮不到它。下图是一个举例分析:
举例分析: 滑动窗口准备滑向元素4,如果滑向元素4,那么比元素4小的原窗口元素就没有存在的必要了,因此弹出,窗口弹出3与-1,然后再弹出超过窗口范围的元素,即9,此时容器中没有元素,然后加入元素4,此时完成滑动。
其实反过来说就是,加入一个新元素j(下图为-1),这个-1比前面的数值都小,有存在的必要吗?是有的,因为在这个时候存在一种可能,即最大值是j前面的元素,但是j前面元素都被淘汰了,j就是最大值。
因此根据上述的特性,当往容器中加入新元素时,因为加入的新元素的下标都是大于前面元素的,下标设为j,即i<j,当此元素比前一个元素的大时,就满足上面的特性,前一个元素就没有什么存在的必要性,可以弹出,直到前面那个元素比新元素大为止(或者容器空了)。
代码如下:
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
LinkedList<Integer> deque = new LinkedList<>();
int[] tar = new int[nums.length - k + 1];
int cnt = 0;
for (int i = 0; i < k; i++) {
while (!deque.isEmpty() && nums[deque.peekLast()] <= nums[i]) {
deque.pollLast();
}
deque.offer(i);
}
tar[cnt] = nums[deque.peekFirst()];
for (int i = k; i < nums.length; i++) {
cnt++;
while (!deque.isEmpty() && nums[deque.peekLast()] <= nums[i]) {
deque.pollLast();
}
deque.offer(i);
while(deque.peekFirst() <= i - k){
deque.pollFirst();
}
tar[cnt] = nums[deque.peekFirst()];
}
return tar;
}
}