剑指Offer 59、滑动窗口的最大值

93 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第8天,点击查看活动详情

题目: 本题依然对应两个子问题。

  1. 给定一个数组nums和滑动窗口大小k,找出滑动窗口的最大值。
  2. 定义一个队列并实现函数max_value得到队列里的最大值,要求函数max_valuepush_backpop_front的均摊时间复杂度都是O(1)O(1)。若队列为空,则pop_frontmax_value都返回-1.

解题思路

对于第一问:最简单的思路是使用一个数据结构将窗口的值保存下来,并且这个数据结构具有一个特性就是可以直接取出数据结构中的最大值,我们只需要在移动窗口的时候删除左边的新增右边的即可,那么这样的数据结构就是大根堆,可得代码如下:

public int[] maxSlidingWindow(int[] nums, int k) {
    if(nums.length == 0) return new int[0];
    int[] res = new int[nums.length - k + 1];
    PriorityQueue<Integer> queue = new PriorityQueue<>(new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            return o2 - o1;
        }
    });
    for(int i=0;i<k;i++){
        queue.offer(nums[i]);
    }
    for(int i=0;i<res.length;i++){
        res[i] = queue.peek();
        queue.remove(nums[i]);
        if(i+k<nums.length) queue.offer(nums[i+k]);
    }

    return res;
}

这里时间复杂度为O(N2)O(N^2),优先级队列remove的时间复杂度为O(n)O(n)。时间复杂度过高。

换个思路:如果数组的下一个元素大于当前元素,那么当前元素则不可能是下一个窗口的最大值,而当数组的下一个元素小于当前元素,则下一个元素可能是之后窗口的最大值,根据此特性,我们可以发现这是一个单调递减队列。可得代码实现:

public int[] maxSlidingWindow2(int[] nums, int k) {
    if(nums.length == 0) return new int[0];
    int[] res = new int[nums.length - k + 1];
    // 单调队列(单调递减队列)
    // 如果当前元素小于当前队尾元素,则其可能是下一个窗口的最大值
    // 如果当前元素大于当前队尾元素,则之前元素不可能是下一个窗口的最大值
    LinkedList<Integer> queue = new LinkedList<>();
    for(int i=0;i<nums.length;i++){
        while(!queue.isEmpty()&&nums[queue.peekLast()]<=nums[i]){
            queue.pollLast();
        }
        queue.addLast(i);
        if(queue.peek()<=i-k) queue.poll();
        if(i+1>=k) res[i+1-k] = nums[queue.peek()];
    }
    return res;
}

此时时间复杂度即为O(n)O(n)

对于第二问,需要关注的是push元素是向队列末尾push,而pop元素是从对头开始,这样如果按照push的时候将push的值和最大值进行绑定的话是不可行的。解决方法还是单调队列,当push的元素大于当前队列的队尾时,则队尾的元素可以直接pop,因为pop是从对头开始的,因此队尾元素必然比push元素先pop,此队列为单调递增队列,可得代码如下:

class MaxQueue{

    private LinkedList<Integer> queue;
    private LinkedList<Integer> max;


    public MaxQueue() {
        queue = new LinkedList<>();
        max = new LinkedList<>();
    }

    public int max_value() {
        if(max.isEmpty()){
            return -1;
        }
        return max.peekFirst();
    }

    public void push_back(int value) {
        queue.offer(value);
        while(!max.isEmpty()&&max.peekLast()<value){
            max.pollLast();
        }
        max.offer(value);
    }

    public int pop_front() {
        if(queue.isEmpty()){
            return -1;
        }
        Integer integer = queue.pollFirst();
        if(integer.equals(max.peekFirst())){
            max.pollFirst();
        }
        return integer;
    }
}