算法题|滑动窗口的最大值

剑指 Offer 59 - I. 滑动窗口的最大值

题目描述:

给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值。

示例:

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


优先队列

官解第一种方法使用的优先队列,这个方法我觉得很妙,于是补充一下细节,加深对题目的理解。

文字描述如下:

创建一个优先队列,不断的向该优先队列中加入数组中的元素。每加入一个元素,视为滑动窗口向右移动一格(此时滑动窗口右指针为i),每次加入元素对该优先队列进行校核。

校核方式为:查看元素中堆顶元素是否属于滑动窗口的范围内,查看的方法为比较堆顶元素下标是否大于滑动窗口左指针下标(i-k)。如果堆顶元素在范围内,无事发生,继续执行下一步指令;如果堆顶元素不在滑动窗口范围内,则弹出该元素,重复校核新堆顶元素,直至堆顶元素满足校核要求。

校核完毕后,将堆顶元素peek一下,放入创建好的数组中即可。

图片描述如下:

 代码如下:

    public int[] maxSlidingWindow(int[] nums, int k) {
        //使用优先队列解题
        //int[num,index]
        PriorityQueue<int[]> queue = new PriorityQueue<>(new Comparator<int[]>() {
            @Override
            public int compare(int[] o1, int[] o2) {
                //比较数值大小,数值大的放前面;如果数值大小相同,比较下标大小,下标大的放前面
                if(o1[0] - o2[0] != 0){
                    return o2[0] - o1[0];//大值在前面,因此o1如果是大值,返回结果要得到负数才可以在前面,因此此处返回时o2-o1
                }else return o2[1] - o1[1];
            }
        });
        int cnt = 0;
        int[] tar = new int[nums.length - k + 1];
        for (int i = 0; i < k; i++) {
            queue.offer(new int[]{nums[i],i});
        }
        int[] peek = queue.peek();
        tar[0] = peek[0];
        for (int i = k; i < nums.length; i++) {
            //窗口往前移动一格,i为右指针位置
            queue.offer(new int[]{nums[i],i});
            //查看堆顶元素是否在窗口中,i-k为左指针的位置(这步很妙)
            while(queue.peek()[1] <= i - k){
                queue.poll();
            }
            cnt++;
            tar[cnt] = queue.peek()[0];
        }
        return tar;
    }

单调队列

除了优先队列,官解还提供了使用单调队列的解法,单调队列很符合本题的要求。

在理解什么是单调队列前,分析一下题目要求:

题目要求是保存每一个滑动窗口内的最大值,我们可以创建一个容器去存储滑动窗口内的元素,不过根据题目要求,我们只要窗口内的最大值就行,即当滑动后出现新的窗口时,只需要保证最大值在容器内存在就可以了,其他数值是否存在不重要。

而且根据这题分析,还有个特性,就当存在两个数iji<j,且nums[i]<nums[j]时,这个下标为i的数就没什么存在的必要了,因为最大值因为有j的存在,永远有轮不到它。下图是一个举例分析:

举例分析: 滑动窗口准备滑向元素4,如果滑向元素4,那么比元素4小的原窗口元素就没有存在的必要了,因此弹出,窗口弹出3与-1,然后再弹出超过窗口范围的元素,即9,此时容器中没有元素,然后加入元素4,此时完成滑动。

其实反过来说就是,加入一个新元素j(下图为-1),这个-1比前面的数值都小,有存在的必要吗?是有的,因为在这个时候存在一种可能,即最大值是j前面的元素,但是j前面元素都被淘汰了,j就是最大值。

 因此根据上述的特性,当往容器中加入新元素时,因为加入的新元素的下标都是大于前面元素的,下标设为j,即i<j,当此元素比前一个元素的大时,就满足上面的特性,前一个元素就没有什么存在的必要性,可以弹出,直到前面那个元素比新元素大为止(或者容器空了)。

代码如下:

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        LinkedList<Integer> deque = new LinkedList<>();
        int[] tar = new int[nums.length - k + 1];
        int cnt = 0;
        for (int i = 0; i < k; i++) {
            while (!deque.isEmpty() && nums[deque.peekLast()] <= nums[i]) {
                deque.pollLast();
            }
            deque.offer(i);
        }
        tar[cnt] = nums[deque.peekFirst()];

        for (int i = k; i < nums.length; i++) {
            cnt++;
            while (!deque.isEmpty() && nums[deque.peekLast()] <= nums[i]) {
                deque.pollLast();
            }
            deque.offer(i);
            while(deque.peekFirst() <= i - k){
                deque.pollFirst();
            }
            tar[cnt] = nums[deque.peekFirst()];
        }
        return tar;
    }
}

​​​​​​​

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值