窗口及窗口最小/大值的更新结构(单调双向队列)

窗口

窗口的数据结构使用的是单调双向队列。滑动时,加数只能从窗口右端(尾部)加,滑出数时数只能从左边(头部)滑出。注意:队列中存的是数组的下标。
以求最大值为例,流程为:

  • 窗口进数时有两种情况:
  1. 新加的数比队列中最后一个数小,则直接在尾部加入队列;
  2. 新加的数比队列中最后一个数大或等于,则从尾部弹出旧数直到队列尾部的数严格大于新加入的数,再把新数加入队列尾部。注意:队列中的值要严格递减,若碰到相同的值则把旧值弹出放入新值的下标。
  • 窗口滑出数时:若窗口滑动时,滑出的数是队列第一个数(最大值),则把最大值从头部弹出;否则说明队列中的最大值还没过期(还没从窗口滑出)。

举例与应用

题目一:固定窗口大小的题

在这里插入图片描述
贴代码:

	public static int[] getMaxWindow(int[] arr, int w) {
        if (arr.length <= 0 || w <= 0 || arr.length < w) return null;
        int[] ans = new int[arr.length - w + 1];
        LinkedList<Integer> queue = new LinkedList<>();
        for (int i = 0; i < arr.length; i++) {
            //加数时
            while (!queue.isEmpty() && arr[queue.peekLast()] <= arr[i]) {
                queue.pollLast();
            }
            queue.add(i);
            //滑出数时
            if (queue.peekFirst() == i - w) {
                queue.pollFirst();
            }
            if (i >= w - 1) {
                ans[i - w + 1] = arr[queue.peekFirst()];
            }
        }
        return ans;
    }

时间复杂度O(n)。

题目二:窗口大小变化的题

在这里插入图片描述
方法一:暴力解,时间复杂度O(n^3)。(两层循环+判断,不贴代码了)
方法二:滑动窗口法。

先提两点:

  1. 如果某一段子数组L~R是达标的,则此子数组内任意的子数组都达标;
  2. 如果某一子数组L~R已经不达标了,则包含此子数组的任意数组都不达标(即将此子数组往外扩形成的数组都不达标)。

先准备一个答案变量res,再准备一个保存滑动窗口最大值的单调双端队列qMax和一个保存滑动窗口最小值的单调双端对列qMin,一个L的指针表示窗口的左端,一个R的指针表示窗口的右端。下面是程序的流程:

  1. L和R先从角标0开始,若满足条件,R往右走,直到不满足条件,此时表示的是以L下标开始的最大窗口。令res += R - L,表示满足条件的子数组中以L开头的子数组的个数;(这里用到是上面的第一点:达标子数组中的任意子数组都达标)
  2. 由于已经不满足条件了,则L向左滑动一格,再次判断R能否向右滑动,重复1的步骤。(这里用到的是上面的第二点:不达标子数组再往外扩仍是不达标的,所以不要直接把R往右扩了,应该先把L向右滑动后再判断R是否能往右扩)

可以看到,L和R都一直往右走不回头,时间复杂度为O(n)。
贴代码:

	public static int getMax(int[] arr, int num) {
        int res = 0;
        if (arr.length == 0) return res;
        LinkedList<Integer> qMax = new LinkedList<>();
        LinkedList<Integer> qMin = new LinkedList<>();
        int L = 0;
        int R = 0;
        while (L < arr.length) {
            while (R < arr.length) {
                while (!qMax.isEmpty() && arr[qMax.peekLast()] <= arr[R]) {
                    qMax.pollLast();
                }
                qMax.addLast(R);
                while (!qMin.isEmpty() && arr[qMin.peekLast()] >= arr[R]) {
                    qMin.pollLast();
                }
                qMin.addLast(R);
                if (arr[qMax.peekFirst()] - arr[qMin.peekFirst()] > num) {
                    break;//不满足条件跳出
                }
                R++;
            }
            res += R - L;
            if (qMax.peekFirst() == L) qMax.pollFirst();
            if (qMin.peekFirst() == L) qMin.pollFirst();
            L++;
        }
        return res;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值