在解决题目之前,我们先来了解一下单调递减队列,它其实就是在队列的基础上多加了一些限制,如下图:
????????? 要求队列中的元素必须按从大到小的顺序排列。
如果向单调递减队列中加入数字 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);
}
@Override
public 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;
}