LeetCode_Queue_239. Sliding Window Maximum 滑动窗口最大值【优先队列,单调队列】【java】【中等】

目录

一,题目描述

英文描述

中文描述

示例与说明

二,解题思路

1,优先队列

2,单调队列

三,AC代码

Java

优先队列

单调对列

四,解题过程

第一搏

第二搏

第三搏


一,题目描述

英文描述

You are given an array of integers nums, there is a sliding window of size k which is moving from the very left of the array to the very right. You can only see the k numbers in the window. Each time the sliding window moves right by one position.

Return the max sliding window.

中文描述

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

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

示例与说明

LeetCode_Queue_239. Sliding Window Maximum 滑动窗口最大值【优先队列,单调队列】【java】【中等】LeetCode_Queue_239. Sliding Window Maximum 滑动窗口最大值【优先队列,单调队列】【java】【中等】

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/sliding-window-maximum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

二,解题思路

参考@力扣官方题解【滑动窗口最大值】

1,优先队列

最直观的解法就是采用优先队列。

队列中包括滑动区间内的所有值。每次取值时,判断队头是否属于滑动区间内的元素,若不是则弹出,否则直接返回即可。

为了方便的判断元素位置,可以考虑在优先队列中存放大小为2的数组(位置1:元素值,位置2:元素下标)。

2,单调队列

优先队列的作用主要是为了获得滑动窗口内的最大值,然而若只是想获得窗口内最大值的话,维持一个普通队列就可以了。

  • 当向队尾插入一个元素时,只需要将队列中小于等于该元素的值从后向前依次弹出即可;
  • 若队头元素不属于滑动区间范围内,则从前向后弹出即可;

这样队头的元素一定是最大,且最新的(位于滑动窗口右侧,相较于左侧淘汰的晚些);

可以从上面看出,队列需要支持队头和队尾操作,因此我们采用双端队列。

此外,为了方便的判断元素的位置,队列中存放的值应该为数组中元素对应的下标(有下标后就可以很方便的获得对应的值)。因此队列中存放的值(数组下标)总保持递增的顺序,也就是通常所说的单调队列。

三,AC代码

Java

优先队列

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        // int[2]:位置0表示元素大小,位置1表示元素位置
        PriorityQueue<int[]> heap = new PriorityQueue<>(new Comparator<int[]>(){
            public int compare(int[] a, int[] b) {
                // 元素大小不等时,按照大根堆的方法比较
                // 元素大小相等时,位置越小,优先级越高,越先被弹出
                return a[0] != b[0] ? b[0] - a[0] : a[1] - b[1];
            }
        });
        int[] ans = new int[nums.length - k + 1];
        for (int i = 0; i < k - 1; i++) {
            heap.offer(new int[]{nums[i], i});
        }
        for (int i = k - 1; i < nums.length; i++) {
            heap.offer(new int[]{nums[i], i});
            while (heap.peek()[1] < i - k + 1) {
                heap.poll();
            }
            ans[i - k + 1] = heap.peek()[0];
        }
        return ans;
    }
}

单调对列

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        Deque<Integer> queue = new LinkedList<>();
        // 将前k-1个数放入队列中
        for (int i = 0; i < k - 1; i++) {
            while (!queue.isEmpty() && nums[i] >= nums[queue.peekLast()]) {// !!!加上等于 !!!这里需要与队列末尾进行比较,而不是队头
                queue.pollLast();
            }
            queue.offerLast(i);
        }
        int[] ans = new int[nums.length - k + 1];
        // 从k-1开始遍历数组
        for (int i = k - 1; i < nums.length; i++) {
            // 若队尾元素小于当前元素,则弹出队尾
            while (!queue.isEmpty() && nums[i] >= nums[queue.peekLast()]) {// !!!加上等于
                queue.pollLast();
            }
            queue.offerLast(i);
            // 将队头超过滑动窗口区间的元素弹出
            while (queue.peekFirst() < i - k + 1) {
                queue.pollFirst();
            }
            ans[i - k + 1] = nums[queue.peekFirst()];
        }
        return ans;
    }
}

四,解题过程

第一搏

维护大小为k的大根堆,滑动窗口每移动一格,删除原窗口中第一个元素(heap.remove(nums[i])),将新窗口最后一个元素添加到堆中,返回堆顶即可。

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        PriorityQueue<Integer> heap = new PriorityQueue<>(new Comparator<Integer>(){
            public int compare(Integer a, Integer b) {
                return b - a;// 大根堆
            }
        });
        int[] ans = new int[nums.length - k + 1];
        for (int i = 0; i < k - 1; i++) {
            heap.offer(nums[i]);
        }
        for (int i = 0; i + k <= nums.length; i++) {
            heap.offer(nums[i + k - 1]);
            ans[i] = heap.peek();
            heap.remove(nums[i]);
        }
        return ans;
    }
}

第二搏

显然,在堆中查找并删除元素是非常耗时的!

考虑要删除的元素是指滑动窗口第一个值,要么根据值来删除(同上,采用remove),要么根据位置来删除。

若堆顶元素的位置不在滑动区间范围内,则将其移除。

因此就需要额外存储元素的位置。

LeetCode_Queue_239. Sliding Window Maximum 滑动窗口最大值【优先队列,单调队列】【java】【中等】

LeetCode_Queue_239. Sliding Window Maximum 滑动窗口最大值【优先队列,单调队列】【java】【中等】

第三搏

优先队列的作用主要是为了获得滑动窗口内的最大值,然而若只是想获得窗口内最大值的话,维持一个普通队列就可以了。

  • 当向队尾插入一个元素时,只需要将队列中小于等于该元素的值从后向前依次弹出即可;
  • 若队头元素不属于滑动区间范围内,则从前向后弹出即可;

这样队头的元素一定是最大,且最新的(位于滑动窗口右侧,相较于左侧淘汰的晚些);

可以从上面看出,队列需要支持队头和队尾操作,因此我们采用双端队列。

此外,为了方便的判断元素的位置,队列中存放的值应该为数组中元素对应的下标(有下标后就可以很方便的获得对应的值)。因此队列中存放的值(数组下标)总保持递增的顺序,也就是通常所说的单调队列。

LeetCode_Queue_239. Sliding Window Maximum 滑动窗口最大值【优先队列,单调队列】【java】【中等】

上一篇:0239-leetcode算法实现之滑动窗口最大值-sliding-window-maximum-python&golang实现


下一篇:PAT 甲级 1007 Maximum Subsequence Sum