每日算法总结——滑动窗口最大/最小值求解

由一个代表题目,引出一种结构

【题目】
有一个整型数组arr和一个大小为w的窗口从数组的最左边滑到最右边,窗口每次向右边滑一个位置。
例如,数组为[4,3,5,4,3,3,6,7],窗口大小为3时:

[4 3 5]4 3 3 6 7	// 窗口中最大值为5
 4[3 5 4]3 3 6 7	// 窗口中最大值为5
 4 3[5 4 3]3 6 7	// 窗口中最大值为5
 4 3 5[4 3 3]6 7	// 窗口中最大值为4
 4 3 5 4[3 3 6]7	// 窗口中最大值为6
 4 3 5 4 3[3 6 7]	// 窗口中最大值为7

如果数组长度为n,窗口大小为w, 则一共产生n-w+1个窗口的最大值。
请实现一个函数。
输入:整型数组arr,窗口大小为w
输出:一个长度为n-w+1的数组resres[i]表示每一种窗口状态下的以本题为例,结果应该返回{5,5,5,4, 6,7}

滑动窗口最大值求解

上述题目中的滑动窗口是固定大小的,那如果滑动窗口可变呢,如何得到窗口内的最大值/最小值

这类题的核心就是单调双端队列,什么是单调双端队列?

  • 是指一种排序后的队列,但该队列首尾均可放入/弹出元素。
  • 严格单调队列:不存在重复元素的单调队列。

对于滑动窗口,我们使用两个指针[L, R]来表示,滑动窗口的改变由用户决定,比如用户可以让L右移或R右移(保证L不超过R

  • 我们的目的就是随时得到窗口内的最大值/最小值,下面以求最大值为例
解题思路
  • 准备一个双端队列,队列中存放的是数组中元素的下标,保证队列中的元素是由大到小(从头到尾)的
  • R向右移:
    • 新加入窗口的元素new会与当前队列中的最小值作比较,如果new大于最小值,则将最小值从队列尾部弹出,然后再让new与当前队列最小值比较……重复……
      • new小于当前最小值时,将new从尾部加入队列
        • 队列为空时,也将new从尾部加入队列
  • L向右移:
    • L右移后,会有一个元素从窗口中出去(arr[L - 1]),此时需要判断当前队列头部的元素下标是否是L - 1,如果是,则将其弹出;不是,就啥也不做(因为下标为L - 1的元素值太小,早就被后面的哥们挤掉了)。

要理解双端队列维持的是什么信息,为什么可以保持最大值?

  • 维持的信息是:如果只让L右移,谁会成为最大值

  • 为什么一个新的元素进入窗口,如果大于当前最小值min,可以将其弹出?

    • 因为L右移的时候,优先弹出的是坐标较小的值,min永远没有可能成为整个队列的最大值(简而言之,新进来的数对min说:我比你大,还比你晚出去,你肯定不会成为最大值了)

复杂度估计:

  • 每个元素进窗口一次,出窗口一次,总代价是 O ( N ) O(N) O(N)

JavaCode

public class SlidingWindowMaxArray {

    public static class WindowMax{
        private int l;
        private int r;
        private int[] arr;
        private LinkedList<Integer> qmax;

        public WindowMax(int[] a) {
            arr = a;
            l = -1;
            r = 0;
            qmax = new LinkedList<>();
        }

        public void addNumFromRight() {
            if (r == arr.length) {
                return;
            }
            while (!qmax.isEmpty() && arr[qmax.peekLast()] <= arr[r]) {
                qmax.pollLast();
            }
            qmax.addLast(arr[r]);
            r++;
        }

        public void removeNumFromLeft() {
            if (l >= r - 1) {
                return;
            }
            l++;
            if (qmax.peekFirst() == l) {
                qmax.pollFirst();
            }
        }

        public Integer getMax() {
            if (!qmax.isEmpty()) {
                return arr[qmax.peekFirst()];
            }
            return null;
        }
    }

    /**
     * 针对上述题
     */
    public static int[] getMaxWindow(int[] arr, int w) {
        if (arr == null || w < 1 || arr.length < w) {
            return null;
        }
        LinkedList<Integer> qmax = new LinkedList<>();
        int[] res = new int[arr.length - w + 1];
        int index = 0;
        for (int i = 0; i < arr.length; i++) {
            // R右移
            while (!qmax.isEmpty() && arr[qmax.peekLast()] <= arr[i]) {
                qmax.pollLast();
            }
            qmax.addLast(arr[i]);
            // L左移
            if (qmax.peekFirst() == i - w) {
                qmax.pollFirst();
            }
            // 填入数据
            if (i >= w - 1) {
                res[index++] = arr[qmax.peekFirst()];
            }
        }
        return res;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值