单调递减队列:
在解决题目之前,我们先来了解一下单调递减队列,它其实就是在队列的基础上多加了一些限制,如下图:
要求队列中的元素必须按从大到小的顺序排列。
如果向单调递减队列中加入数字 1,可以直接加入,不会改变队列中递减的要求。
但是向队列中加入数字 3 就不能直接加入了。需要把队列中的 2,1 移除,然后再加入 3。
我们可以利用java中自带的LinkedList双端队列来实现一下单调递减队列。
import java.util.LinkedList;//单调递减队列
public class MonotonicQueue<T extends Comparable<T>> {private final LinkedList<T> queue = new LinkedList<>();public T peek(){return queue.peekFirst();}public void poll(){queue.pollFirst();}public void offer(T t){while(!queue.isEmpty() && queue.peekLast().compareTo(t) < 0){queue.pollLast();}queue.offerLast(t);}@Overridepublic String toString() {return queue.toString();}public static void main(String[] args) {MonotonicQueue<Integer> q = new MonotonicQueue<>();for(int i : new int[]{1, 3, -1, -3, 5, 3, 6, 7}){q.offer(i);System.out.println(q);}}
}
接下来我们用单调递减队列来解决力扣的一道题目
例题:
分析:
题目说了,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。假设滑动窗口的大小 k = 3,每次窗口右移,找出滑动窗口里的最大值并把它填入一个新数组中(滑动窗口必须被填满)。
我们可以使用单调递减队列来找到滑动窗口中的最大值,每次向单调递减队列加入元素,队列的队头元素就是滑动窗口里面的最大值。如下图(从左往右看)。
注意:只有当滑动窗口被填满时,才获取窗口里面的最大值。
有一种情况需要注意,如下图:
当队列中的元素超过滑动窗口的范围( k ),要及时把队头元素移除。
这里可以利用索引,当遍历到第 i 个索引时,i - k 处的元素就是过期的元素,应该移除。
也就是满足了 nums[i - k] == queue.peek() ,则移除队头元素。
注意:这里不能用队列大小当判断条件,当队列长度大于窗口大小(k)就移除元素,这样做可能会出问题。
如果把上图中的数字 -4 改为 1,当往队列加入1时,会把前面的-1,-3覆盖掉,此时队列长度小于滑动窗口值,用单调递减队列找到的最大值(数字3) 其实是过期的。
显然,此时滑动窗口的最大值为1。
代码实现:
package leetcode;import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;public class SlidingWindowMaxnum {public static int[] maxSlidingWindow(int[] nums, int k) {//创建单调递减队列MonotonicQueue<Integer> queue = new MonotonicQueue<>();List<Integer> list = new ArrayList<>();for (int i = 0; i < nums.length; i++) {//检查队列头部元素,超过滑动窗口范围要移除if(i >= k && nums[i - k] == queue.peek()){queue.poll();}int num = nums[i];queue.offer(num);if(i >= k - 1){list.add(queue.peek());}}return list.stream().mapToInt(Integer::intValue).toArray();}public static void main(String[] args) {System.out.println(Arrays.toString(maxSlidingWindow(new int[]{1, 3, -1, -3, 5, 3, 6, 7}, 3))); //[3, 3, 5, 5, 6, 7]//System.out.println(Arrays.toString(maxSlidingWindow(new int[]{7, 2, 4}, 2))); // [7, 4]//System.out.println(Arrays.toString(maxSlidingWindow(new int[]{1, 3, 1, 2, 0, 5}, 3))); // [3, 3, 2, 5]//System.out.println(Arrays.toString(maxSlidingWindow(new int[]{-7, -8, 7, 5, 7, 1, 6, 0}, 4))); // [7, 7, 7, 7, 7]}
}
可以把上面的集合换成数组,做一个小小的优化,放到力扣上跑会更好。
public static int[] maxSlidingWindow(int[] nums, int k) {MonotonicQueue<Integer> q = new MonotonicQueue<>();int[] output = new int[nums.length - (k - 1)];for (int i = 0; i < nums.length; i++) {if (i >= k && nums[i - k] == q.peek()) {q.poll();}int num = nums[i];q.offer(num);if (i >= k - 1) {output[i - (k - 1)] = q.peek();}}return output;}