剑指offer-滑动窗口的最大值

题目描述

  • 给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。
  • 地址:牛客链接

问题分析

  • 非常经典的滑动窗口问题,只不过该题是固定窗口大小,相对简单一点
  • 如何维护滑动窗口的最大值?
    • 维护最大值时,用双端队列,队列内存放数组索引(因为原数组中可能会有重重复元素,存index则不会重复),使索引位置上的数从大到小
    • 当right向右移动时,窗口加数只有当队列非空,并且队尾元素小于等于待加入元素时,弹出队尾元素(等于是为了当出现重复元素时,让索引是最新索引(靠后的索引))。直至队尾元素大于待加入元素。将待加入元素加入队尾。
    • 当left向右移动时,窗口减数检查过期的index是否是队首元素,若是,则将队首元素从队首出队。
  • 如何维护滑动窗口的最小值
    • 维护最小值时,用双端队列,队列内存放数组索引,使索引位置上的数从小到大
    • 当right向右移动时,窗口加数。只有当队列非空,并且队尾元素大于等于待加入元素时,弹出队尾元素。直至队尾元素小于待加入元素。将待加入元素加入队尾。
    • 当left向右移动时,窗口减数。检查过期的index是否是队首元素,若是,则将队首元素从队首出队。
  • 回到问题本身上来,本题方法1便是用了left 与right 来指示一个窗口左边界和右边界。但由于窗口大小是固定的,所以也可以只用一个值来确定窗口右边界即可。只要在右边界向右走之后,检查maxWin里面维护的最大值是否已经出窗即可。如果i - winMax.peekFirst() + 1 > size 说明 num[winMax.peekFirst()] 已经出窗,所以需要将winMax中第一个元素出队。

经验教训

  • 滑动窗口内的最大值
  • 滑动窗口内的最小值

代码实现

  • left 与 right 法
    public ArrayList<Integer> maxInWindows1(int [] num, int size) {
        ArrayList<Integer> res = new ArrayList<>();
        if (num == null || size <= 0 || num.length < size) {
            return res;
        }
        //left,right用来指示窗口的大小
        int left = 0;
        int right = 0;
        LinkedList<Integer> winMax = new LinkedList<>();
        //先装入size个
        while(right - left <= size - 1) {
            //使winMax中从前到后存储的index,它们所指示的元素从大到小的。
            while (! winMax.isEmpty() && num[winMax.peekLast()] <= num[right]) {
                winMax.pollLast();
            }
            winMax.addLast(right);
            ++right;
        }
        //得到第一个窗口的值
        res.add(num[winMax.peekFirst()]);
        while (right < num.length) {
            //此时,开始计算一个新窗口,right已经后移,需要将left同样后移,这样才是一个新窗口
            //左边出窗
            //若最大值索引已经被移出窗外,那么需要从winMax中删除
            int expireIndex = left;
            if (expireIndex == winMax.peekFirst()) {
                winMax.pollFirst();
            }
            //left后移
            ++left;
            //右边入窗
            while (! winMax.isEmpty() && num[winMax.peekLast()] <= num[right]) {
                winMax.pollLast();
            }
            winMax.addLast(right);
            //将新窗口的最大值加入结果集中
            res.add(num[winMax.peekFirst()]);


            //right后移,计算下一窗口
            ++right;
        }
        return res;
    }
  • 简洁法
    public ArrayList<Integer> maxInWindows(int [] num, int size) {
        if (num == null || size <= 0 || num.length < size) {
            return new ArrayList<Integer>();
        }
        ArrayList<Integer> res = new ArrayList<>();
        LinkedList<Integer> winMax = new LinkedList<>();
        //i便可以看做窗口的右边界
        for (int i = 0; i < num.length; i++) {
            //当winMax队首元素所指向的元素因为i的增加不在窗口内时,需要从winMax中移除
            if (!winMax.isEmpty() && i - winMax.peekFirst() + 1 > size) {
                winMax.pollFirst();
            }
            //右边入窗
            while(!winMax.isEmpty() && num[winMax.peekLast()] <= num[i]) {
                winMax.pollLast();
            }
            winMax.addLast(i);
            //只有当形成size大小窗口时,才加入到结果集中
            if (i >= size - 1) {
                res.add(num[winMax.peekFirst()]);
            }
        }
        return res;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值