滑动窗口最大值(力扣239题)

单调递减队列:

在解决题目之前,我们先来了解一下单调递减队列,它其实就是在队列的基础上多加了一些限制,如下图:

滑动窗口最大值(力扣239题)_第1张图片          要求队列中的元素必须按从大到小的顺序排列

如果向单调递减队列中加入数字 1,可以直接加入,不会改变队列中递减的要求。

滑动窗口最大值(力扣239题)_第2张图片

但是向队列中加入数字 3 就不能直接加入了。需要把队列中的 2,1 移除,然后再加入 3。  

滑动窗口最大值(力扣239题)_第3张图片

我们可以利用java中自带的LinkedList双端队列来实现一下单调递减队列。

import java.util.LinkedList;

//单调递减队列
public class MonotonicQueue> {

    private final LinkedList 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 q = new MonotonicQueue<>();
        for(int i : new int[]{1, 3, -1, -3, 5, 3, 6, 7}){
            q.offer(i);
            System.out.println(q);
        }
    }
}

接下来我们用单调递减队列来解决力扣的一道题目

例题:

滑动窗口最大值(力扣239题)_第4张图片

分析:

题目说了,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。假设滑动窗口的大小 k = 3,每次窗口右移,找出滑动窗口里的最大值并把它填入一个新数组中(滑动窗口必须被填满

滑动窗口最大值(力扣239题)_第5张图片滑动窗口最大值(力扣239题)_第6张图片


滑动窗口最大值(力扣239题)_第7张图片滑动窗口最大值(力扣239题)_第8张图片

我们可以使用单调递减队列来找到滑动窗口中的最大值,每次向单调递减队列加入元素,队列的队头元素就是滑动窗口里面的最大值。如下图(从左往右看)。

滑动窗口最大值(力扣239题)_第9张图片滑动窗口最大值(力扣239题)_第10张图片

滑动窗口最大值(力扣239题)_第11张图片滑动窗口最大值(力扣239题)_第12张图片

注意:只有当滑动窗口被填满时,才获取窗口里面的最大值。

有一种情况需要注意,如下图:

滑动窗口最大值(力扣239题)_第13张图片

当队列中的元素超过滑动窗口的范围( k ),要及时把队头元素移除。

这里可以利用索引,当遍历到第 i 个索引时,i - k 处的元素就是过期的元素,应该移除。

也就是满足了 nums[i - k] == queue.peek() ,则移除队头元素。

注意:这里不能用队列大小当判断条件,当队列长度大于窗口大小(k)就移除元素,这样做可能会出问题。

滑动窗口最大值(力扣239题)_第14张图片

如果把上图中的数字 -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 queue = new MonotonicQueue<>();
        List 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 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;
    }

你可能感兴趣的:(leetcode,算法)