窗口内最大值或者最小值的更新结构(单调双向队列)

首先我们需要一个双向链表作为队列。例如一个数组{5,4,1,2,6},下面是下标;

这个数组有两个指针,一个指向左边界,一个是指向右边界,这个范围就是窗口。并且left和right只能向右前进不能够后退。

现有一个需求就是无论窗口是啥,你都要给我找到当前的最大值。也就是这个窗口的最大值。

现在搞一个双向队列(值为下标数值):

起初只有哦一个值,那么5就进入到队列,从后进入队列,right++,这时候遇到4,比5小那么也进入队列:

right++,遇到1。也进入队列,这样如果想要获取窗口最大值就很简单了,直接获取头部就可以了。

left++,那么5就出队列。

此时遇到数值2,要比队尾下表为2数值为1的数大,怎么办?弹出所有比2小的值,再加入2:

right++,遇到6,此时队列中没有比这个值的大,那么就全弹出,加入6

为什么要存入下标呢?存入值不行吗?不可以,因为如果出现重复的值,你怎么保证是哪个呢,当我们left++的时候,会取拿出最大的元素的下标,比较是否与当前的相同,看是否过期,如果过期才剔除,否则是不做任何操作的。

综上所述:所有流程如下:

(1)右边界扩充的时候,查看队列尾部的值是否大于要扩展的值,如果大于,直接加入队尾,否则弹出所有比要加入值小的数。

(2)左边界收缩的时候,检查当前队首元素(最大值)是否过期,就是检查下标和当前的left的关系。如果过期才弹出,否则不做任何操作。

(3)如果遇到相同数值,那么也需要弹出大于等于的所有值。

看一个题

当前数组是arr,窗口大小固定为3,找出所有窗口的最大值并组成一个数组。

例如:答案输出[5,4,6,8,8]

用我们单调双向队列是不是容易多了?

public class Main {

    public static void main(String[] args) {
        int[] arr = {5,4,1,2,6,8,4};
        arr = fuck(arr,3);
    }

    public static int[] fuck(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++){
            //如果大于队尾值,弹出
            while(!qmax.isEmpty() && arr[qmax.peekLast()] <= arr[i]){
                qmax.pollLast();
            }
            //加入i下标
            qmax.addLast(i);
            if(qmax.peekFirst() == i - w){
                qmax.pollFirst();
            }
            if(i >= w - 1){
                res[index++] = arr[qmax.peekFirst()];
            }
        }
        return res;
    }


}

在看一个题

最大值减去最小值小于或等于num的子数组数量
给定数组arr和整数num,共返回有多少个子数组满足如下情况:
max(arr[i..j]) - min(arr[i..j]) <= num
max (arr[i.. j])表示子数组arr[i.. j]中的最大值,min(arr[i.. j])表示子数组arr[i.. j]中的最小值。
[要求]
如果数组长度为N,请实现时间复杂度为0 (N)的解法。

这个题暴力破解的时间复杂度是O(n3)。十分耗时间

我们采用单调双向队列就可以解决啦。我们可以搞两个队列,一个是单点最大双向队列,一个是单迪最小双向队列。分别能够取到最大值和最小值,然后跟num比较就可以了!!!假设当前数组窗口是 arr[i ....... j]

注意点:

(1)如果当前的窗口满足条件,那么它的子数组一定符合和满足条件!!!因为任意子数组的最大值和最小值一定比当前窗口的差值更小。

(2)如果当前数组不满足条件,那么数组再向外扩展,一定不满足条件,差值一定大于num。

基于以上两点:我们来撸一个算法。

import java.util.LinkedList;
import java.util.Stack;

public class Main {

    public static void main(String[] args) {
        int[] arr = {5, 4, 1, 2, 6, 8, 4};
        System.out.println(fuck(arr, 3));
    }

    public static int fuck(int[] arr, int num) {
        if (arr == null || arr.length == 0) {
            return 0;
        }
        LinkedList<Integer> qmax = new LinkedList<>();
        LinkedList<Integer> qmin = new LinkedList<>();
        int i = 0, j = 0, res = 0;
        while (i < arr.length) {
            while (j < arr.length) {
                while (!qmax.isEmpty() && arr[qmax.peekLast()] <= arr[j]) {
                    qmax.pollLast();
                }
                qmax.addLast(j);
                while (!qmin.isEmpty() && arr[qmin.peekLast()] >= arr[j]) {
                    qmin.pollLast();
                }
                qmin.addLast(j);
                if (arr[qmax.getFirst()] - arr[qmin.getFirst()] >= num) {
                    break;
                }
                j++;
            }
            if (qmax.getFirst() == i) {
                qmax.pollFirst();
            }
            if(qmin.getFirst() == i){
                qmin.pollFirst();
            }
            res += j - i ;
            i++;
        }
        return res;

    }


}


 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值