• 算法18最大值减去最小值小于或等于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 和 m,代表 n*m 的矩阵
    接下来输入一个 n*m 的矩阵

    输出描述:

    输出给定数组中满足条件的子数组个数

    示例1

    输入:
    5 2 
    1 2 3 4 5
    输出:
    12

    思路

    首先介绍普通的解法,找到arr的所有子数组,一共有O(N^2) 个,然后对每一个子数组做遍历找到其中的最小值和最大值,这个过程的时间复杂度为O(N),然后看看这个子数组是否满足条件,统计所有满足的子数组数量即可,普通解法容易实现,但是时间复杂度为O(N^3),本次不再详述,最优解可以做到时间复杂度为O(N),额外空间复杂度为O(N),在阅读下面的分析过程之前,请读者先阅读“生成窗口最大值数组”问题,本题所使用到的双端队列结构与解决“生成窗口最大值数组”问题中的双端队列结构含义基本一致。
      生成两个双端队列qmax和qmin。当子数组为arr时,qmax维护了窗口子数组arr的最大值更新的结构,qmin维护了窗口子数组arr的最小值更新的结构。当子数组arr[i..j]向右扩一个位置变成arr[i..j+1]时,qmax和qmin结构更新代价的平均时间复杂度为O(1),并且可以在O(1)的时间内得到arr[i..j+1]的最大值和最小值,当子数组arr[i..j]向左缩一个位置变成arr[i+1..j]时,qmax和qmim结构更新代价的平均时间复杂度为O(1),并且在O(1)的时间内得到arr[i+1..j]的最大值和最小值。
      通过分析题目满足的条件,可以得到如下两个结论。
       ● 如果子数组arr[i..j]满足条件,即 max(arr[i..j]) - min(arr[i..j] <= num,那么 arr[i..j]中的每一个子数组,即arr[k..l](i <= k <=l <= j)都满足条件。我们以子数组arr[i..j-1]为例说明,arr[i..j-1]最大值只可能小于或等于arr[i..j]的最大值,arr[i..j-1]最小值只可能大于或等于arr[i..j]的最小值,所以arr[i..j-1]必然满足条件。同理,arr[i..j]中的每一个子数组都满足条件。
       ● 如果子数组arr[i..j]不满足条件,那么所有包含arr[i..j]的子数组,即arr[k..l] (k <= i <= j <= l)都不满足条件。证明过程同第一个结论。
      根据双端队列qmax和qmin的结构性质,以及如上两个结论,设计整个过程如下:
      1.生成两个双端队列qmax和qmin。含义如上文所说,生成两个整型交量 i 和 j ,表示子数组的范围,即arr[i..j]生成整型变量res,表示所有满足条件的子数组数量。
      2.令 j 不断向右移动(j++),表示arr[i..j]一直向右扩大,并不断更新qmax和qmin结构,保证qmax和qmin始终维持动态窗口最大值和最小值的更新结构。一旦出现arr[i..j]不满足条件的情况,j 向右扩的过程停止,此时arr[i..j-1]、arr[i..j-2]、arr[i..j-2]...arr[i..i]一定都是满足条件的。也就是说所有必须以arr[i+1]作为第一个元素的了数组,满足条件的数量为 j - i个,于是令 res += j-i。
      3.当进行完步骤2,令 i 向右移动一个位置,并对 qmax 和 qmin 做出相应的更新,qmax 和 qmin 从原来的 arr[i..j] 窗口变成arr[i+1..j]窗口的最大值和最小值的更新结构。然后重复步骤2,也就是求所有必须以arr[i+1]作为第一个元素的子数组中,满足条件的数量有多少个。
      4.根据步骤2和步骤3,依次求出:必须以arr[0]开头的子数组,满足条件的数量有多少个;必须以arr[1]开头的子数组,满足条件的数量有多少个;必须以arr[2]开头的子数组,满足条件的数量有多少个,全部累加起来就是答案。
      上述过程中,所有的下标值最多进qmax和qminー次,出qmax和qmin一次,i 和 j 值也不断增加,并且从来不减小。所以整个过程的时复杂度为O(N)。

    代码如下:

    import java.util.LinkedList;
    import java.util.Scanner;
    
    public class Main{
        
        public static int getNum(int[] arr,int num) {
            if(arr==null || arr.length == 0) return 0;
            LinkedList<Integer> qmin = new LinkedList<>();
            LinkedList<Integer> qmax = new LinkedList<>();
            
            int i=0,j=0,res=0;
            while(i<arr.length) {
                while(j<arr.length) {
                    if(qmin.isEmpty()||qmin.peekLast()!=j){
                        while(!qmin.isEmpty()&& arr[qmin.peekLast()]>=arr[j]) {
                            qmin.pollLast();
                        }
                        qmin.addLast(j);
                        while(!qmax.isEmpty()&& arr[qmax.peekLast()]<=arr[j]){
                            qmax.pollLast();
                        }
                        qmax.addLast(j);
                    }
                    if(arr[qmax.getFirst()] - arr[qmin.getFirst()]>num)
                        break;
                    j++;
                }
                res +=j - i;
                if(qmin.peekFirst()==i) qmin.pollFirst();
                if(qmax.peekFirst()==i) qmax.pollFirst();
                i++;
            }
            return res;
        }
        
        
        public static void main(String args[]) {
            
            Scanner sc = new Scanner(System.in);
            int n = sc.nextInt();
            int num = sc.nextInt();
            int[] arr = new int[n];
            
            for(int i=0;i<n;i++) {
                arr[i] = sc.nextInt();
            }
            
            int res = getNum(arr,num);
            System.out.println(res);
            
        }
    }
  • 相关阅读:
    JavaScript节点属性
    main函数的参数
    CGI
    open()函数 linux中open函数使用
    海伦公式
    C语言字符串操作函数
    makefile(一)
    makefile
    第一天
    时间小憩
  • 原文地址:https://www.cnblogs.com/sfnz/p/16443194.html
Copyright © 2020-2023  润新知