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

博客介绍了如何解决滑动窗口最大值问题,提供了两种解题思路:使用优先队列和单调队列。对于优先队列,通过自定义比较器实现大根堆,每次移动窗口时更新堆;单调队列则利用双端队列,维护队头始终为最大值,同时保证元素下标有序,避免无效操作。AC代码分别展示了两种方法的实现。
摘要由CSDN通过智能技术生成

目录

一,题目描述

英文描述

中文描述

示例与说明

二,解题思路

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)
链接: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),要么根据位置来删除。

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

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

第三搏

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

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

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

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值