力扣Q239滑动窗口最大值SlidingWindowMaximum

Q239SlidingWindowMaximum

简介

给你一个整数数组 nums ,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

返回 滑动窗口中的最大值 。

示例 1:

输入: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

示例 2:

输入:nums = [1], k = 1
输出:[1]

https://leetcode.cn/problems/sliding-window-maximum

解题

优先队列

考虑到题目要求是取一段内容里的最大值,可以考虑优先队列,即按一定顺序排列的队列。将区间内数字放到队列中,队头即区间最大值,在滑动窗口时不断移除不在区间内的元素,添加新的元素即可。但这种方法会超时,问题就出现在移除队列的时候,最坏的时间复杂度有 O(N2logn),要考虑更低时间复杂度的方法。
主要优化删除时候的耗时,像前面提到的删除方法是每走一步便要删除一次,而且是遍历删除,要想到当队首元素(最大的元素)不需要被删除时,其余元素也可不删除,因为总有元素比它大,只有当队首元素移动到区间左侧的时候,才需要删除,此时也可从队首开始删除,不需要遍历删除,不断移除队首元素直到队首元素确切的在区间中即可。
由于需要判断队首元素是否在区间外,需要在队列的元素中添加元素位置,构成<元素, 元素位置> 这样一个二元组放到优先队列里面。
时间复杂度为 O(nlogn)

代码如下

public int[] maxSlidingWindow(int[] nums, int k) {
        PriorityQueue<int[]> queue = new PriorityQueue<>(((o1, o2) -> o2[0] - o1[0]));      //队列按降序排序
        int len = nums.length;
        int i = 0;
        while (i < k){      //队列初始化
            queue.add(new int[]{nums[i], i});
            i++;
        }
        int[] rtn = new int[len - k + 1];
        rtn[0] = queue.peek()[0];
        while (i < len){
            queue.add(new int[]{nums[i], i});       //保证队列不空
            while (queue.peek()[1] <= i - k) {      //当队首元素不在区间范围内是,不断删除队首元素直到符合标准
                queue.poll();
            }
            rtn[i-k+1]=queue.peek()[0];
            i++;
        }
        return rtn;
    }

提交结果

图 1

单调队列

单调队列是对上面方法的优化,使用双端队列,对队头和队尾操作,时间比优先队列(大小根堆)要快一点。

  • 队列中依然是从大到小排列
  • 每移动一步,将新值与队尾元素比较,不断出队到队列空或新值小于队尾元素。此时就可出队是因为新值在队列原有元素右侧,一定会晚于元素元素出队,故原有元素可以删除。
  • 移除不在区间的元素,可将元素与队首元素比较,如果相同则移除队首元素,不同可不做处理。因为如果要移除的元素是剩余区间最大的且比队首元素小,那么在队首元素插入的时候要移除的元素已经被移除了,不会再队列中存在

代码

public class Q239SlidingWindowMaximum {

    static class MyQueue{       //封装一个新类,也可写入主代码里面
        Deque<Integer> queue = new LinkedList<>();

        void push(int val){     // 插入操作
            while(!queue.isEmpty() && val > queue.getLast()){
                queue.removeLast();
            }
            queue.addLast(val);
        }

        void poll(int val){     //移除操作
            if (!queue.isEmpty() && val == queue.getFirst()){
                queue.removeFirst();
            }
        }

        int peek(){             //取队首
            return queue.getFirst();
        }
    }

    public int[] maxSlidingWindow(int[] nums, int k) {
        if (nums.length == 1 || k == 1){
            return nums;
        }

        MyQueue queue = new MyQueue();

        int len = nums.length - k + 1;
        int[] rtn = new int[len];
        int num = 0;

        for (int i = 0; i < k; i++) {
            queue.push(nums[i]);
        }
        rtn[num++] = queue.peek();

        for (int i = k; i < nums.length; i++) {
            queue.poll(nums[i - k]);
            queue.push(nums[i]);
            rtn[num++] = queue.peek();
        }
        return rtn;
    }

}

提交结果

图 2
可看到时间较前一方法有所缩短

posted @ 2022-08-11 23:15  Mr耀  阅读(59)  评论(0)    收藏  举报