• 最大值和最小值之差达标的子数组数量


    1、题目

    给定一个整型数组 arr,和一个整数 num

    某个 arr 中的子数组 sub,如果想达标,必须满足:sub中最大值 - sub中最小值 <= num。

    请返回 arr 中达标子数组的数量。

    2、思路

    结论1:如果 [L...R] 范围上的 max - min <= sum,则 L...R 范围上的所有子数组都是达标的。

    解释:假设 [L'...R'][L...R] 的子数组
    [L...R]max - [L...R]min <= sum
    [L'...R']max' <= [L...R]max[L'...R']min >= [L...R]min
    因此:[L'...R']max' - [L'...R']min <= sum,依然是达标的。

    结论2:如果 [L...R] 范围上的 max - min > sum,那么无论 L 往左扩还是 R 往右扩,产生的新的子数组一定是不达标的。

    思路:准备一个最大值更新结构qmax 和一个最小值更新结构 qmin,用于维护最大值和最小值。

    假设窗口一开始为0,此时 L = 0,让 R 往右扩,每扩一次就更新 qmaxqmin,如果达标就继续扩,一直到位置 p p p 不达标了,那么必须以 0 位置开始的子数组达标的数量一共有 p − 0 + 1 = p + 1 p - 0 + 1 = p + 1 p0+1=p+1 个。

    然后 L 往右滑动到1,即 L = 1 时,R 继续往右扩,看是否达标,直到到达不能达标的位置,则能得到必须以 1 位置开始的子数组的达标数量。

    注意:该过程中 LR 都没有回退,一共只会滑过 n 个数,所以时间复杂度是 O ( n ) O(n) O(n)

    • C++ 版
    /*************************************************************************
    	> File Name: 002.最大值和最小值差达标的子数组数量.cpp
    	> Author: Maureen 
    	> Created Time: 四 11/10 18:06:16 2022
     ************************************************************************/
    
    #include 
    #include 
    #include 
    using namespace std;
    
    //暴力对数器 O(n^3)
    int right(vector<int> &arr, int sum) {
        if (arr.size() == 0 || sum < 0) {
            return 0;
        }
    
        int n = arr.size();
        int count = 0;
    
        for (int l = 0; l < n; l++) {
            for (int r = l; r < n; r++) {
                int maxV = arr[l];
                int minV = arr[l];
                for (int i = l + 1; i <= r; i++) {
                    maxV = max(maxV, arr[i]);
                    minV = min(minV, arr[i]);
                }
    
                if (maxV - minV <= sum)
                    count++;
            }
        }
    
        return count;
    }
    
    //滑动窗口 O(n)
    int way(vector<int> &arr, int sum) {
        if (arr.size() == 0 || sum < 0) {
            return 0;
        }
    
        deque<int> qmax; //最大值的更新结构
        deque<int> qmin; //最小值的更新结构
    
        int n = arr.size();
        int count = 0;
        int r = 0;
    
        for (int l = 0; l < n; l++) { //枚举子数组的开始位置l
            while (r < n) {
                //最大值的更新
                while (!qmax.empty() && arr[qmax.back()] <= arr[r]) {
                    qmax.pop_back();
                }
                qmax.push_back(r);
                
                //最小值的更新
                while (!qmin.empty() && arr[qmin.back()] >= arr[r]) {
                    qmin.pop_back();
                }
                qmin.push_back(r);
    
                //该子数组是否达标
                if (arr[qmax.front()] - arr[qmin.front()] > sum) { //不达标
                    break;
                } else {
                    r++;
                }
            }
    
            count += r - l;//记录子数组达标的数量
    
            //检查队首元素是否过期
            if (qmax.front() == l) {
                qmax.pop_front();
            }
    
            if (qmin.front() == l) {
                qmin.pop_front();
            }
        }
    
        return count;
    }
    
    vector<int> generateRandomArray(int maxL, int maxV) {
        int len = rand() % (maxL + 1);
        vector<int> arr(len);
        for (int i = 0; i < len; i++) {
            arr[i] = (rand() % (maxV + 1)) - (rand() % (maxV + 1));
        }
    
        return arr;
    }
    
    void printArray(vector<int> &arr) {
        if (arr.size()) {
            for (int i = 0; i < arr.size(); i++) {
                cout << arr[i] << " ";
            }
            cout << endl;
        }
    } 
    
    int main() {
        int maxL = 100;
        int maxV = 200;
        int testTime = 100000;
        cout << "Begin to test: " << endl;
        for (int i = 0; i < testTime; i++) {
            vector<int> arr = generateRandomArray(maxL, maxV);
            int sum = rand() % (maxV + 1);
            int ans1 = right(arr, sum);
            int ans2 = way(arr, sum);
            if (ans1 != ans2) {
                cout << "Oops!" << endl;
                printArray(arr);
                cout << sum << endl;
                cout << ans1 << endl;
                cout << ans2 << endl;
                break;
            }
            if (i % 100 == 0) cout << i << " cases passed!" << endl;
        }
    
        cout << "Test end!" << endl;
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • Java 版
    public class AllLessNumSubArray {
    
    	// 暴力的对数器方法 O(N^3),枚举所有子数组
    	public static int right(int[] arr, int sum) {
    		if (arr == null || arr.length == 0 || sum < 0) {
    			return 0;
    		}
    		int N = arr.length;
    		int count = 0;
    		for (int L = 0; L < N; L++) {
    			for (int R = L; R < N; R++) {
    				int max = arr[L];
    				int min = arr[L];
    				for (int i = L + 1; i <= R; i++) {
    					max = Math.max(max, arr[i]);
    					min = Math.min(min, arr[i]);
    				}
    				if (max - min <= sum) {
    					count++;
    				}
    			}
    		}
    		return count;
    	}
    
        //优化:O(N^3) -> O(N)
        
        // 结论1:如果[L...R] 范围上的max - min <= sum,那么 L...R 范围的所有子数组都是达标的
        //	原因:假设 L'...R'是L...R的子数组,因为[L...R]max - [L...R]min <= sum,那么[L'...R']的max'<= max,而[L'...R']的min' >= min,所以max'- min' <= sum,也是达标的
        //结论2:如果[L...R] 范围上的max - min > sum,那么无论L往左扩还是R往右扩,产生的新的子数组一定是不达标的
        
        //思路:个准备一个最大值和最小值的更新结构qmax和qmin,用于维护最大值和最小值
        //假设窗口一开始为0,此时L = 0,让R往右扩,每扩一次就更新qmax和qmin,如果达标就继续扩,一直到p位置不达标了,那么必须以0开始的子数组达标的数量一共有p-0+1 = p+1个;
        //然后L往右滑动到1,即L=1的时候,R继续往右扩,看是否达标,直到到达不能达标的位置,则能得到必须以1开始的子数组的达标数量;
        //......
    	public static int num(int[] arr, int sum) {
    		if (arr == null || arr.length == 0 || sum < 0) {
    			return 0;
    		}
    		int N = arr.length;
    		int count = 0;
    		LinkedList<Integer> maxWindow = new LinkedList<>();
    		LinkedList<Integer> minWindow = new LinkedList<>();
    		int R = 0; //不回退
            //L和R位置都不回退,一共只过N个数,所以时间复杂度是O(n)
    		for (int L = 0; L < N; L++) {  //依次尝试窗口以0开始[0...、[1...、[2...的情况
                //[L,R),如果L=R,说明窗口内没数
    			while (R < N) { //[L...R,R往右扩,直到初次不达标停止
    				while (!maxWindow.isEmpty() && arr[maxWindow.peekLast()] <= arr[R]) {
    					maxWindow.pollLast();
    				}
    				maxWindow.addLast(R);
    				while (!minWindow.isEmpty() && arr[minWindow.peekLast()] >= arr[R]) {
    					minWindow.pollLast();
    				}
    				minWindow.addLast(R);
    				if (arr[maxWindow.peekFirst()] - arr[minWindow.peekFirst()] > sum) {
    					break;
    				} else {
    					R++;
    				}
    			}
    			count += R - L;
                //如果L过期,则滚蛋
    			if (maxWindow.peekFirst() == L) {
    				maxWindow.pollFirst();
    			}
    			if (minWindow.peekFirst() == L) {
    				minWindow.pollFirst();
    			}
    		}
    		return count;
    	}
    
    	// for test
    	public static int[] generateRandomArray(int maxLen, int maxValue) {
    		int len = (int) (Math.random() * (maxLen + 1));
    		int[] arr = new int[len];
    		for (int i = 0; i < len; i++) {
    			arr[i] = (int) (Math.random() * (maxValue + 1)) - (int) (Math.random() * (maxValue + 1));
    		}
    		return arr;
    	}
    
    	// for test
    	public static void printArray(int[] arr) {
    		if (arr != null) {
    			for (int i = 0; i < arr.length; i++) {
    				System.out.print(arr[i] + " ");
    			}
    			System.out.println();
    		}
    	}
    
    	public static void main(String[] args) {
    		int maxLen = 100;
    		int maxValue = 200;
    		int testTime = 100000;
    		System.out.println("测试开始");
    		for (int i = 0; i < testTime; i++) {
    			int[] arr = generateRandomArray(maxLen, maxValue);
    			int sum = (int) (Math.random() * (maxValue + 1));
    			int ans1 = right(arr, sum);
    			int ans2 = num(arr, sum);
    			if (ans1 != ans2) {
    				System.out.println("Oops!");
    				printArray(arr);
    				System.out.println(sum);
    				System.out.println(ans1);
    				System.out.println(ans2);
    				break;
    			}
    		}
    		System.out.println("测试结束");
    
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
  • 相关阅读:
    首都博物京韵展,监测系统实现文物科技保护
    【0141】【创建postgres后端进程】postgres进程process title的设计机制(10)
    垃圾回收器-CMS及常用回收器分析
    基于SpringBoot+Vue+uniapp的OA办公系统(源码+lw+部署文档+讲解等)
    网络安全-黑客技术-小白学习
    对象数组去重
    OpenFeign远程调用实现
    Android开发中常用单位转换的工具类
    Sentinel流控规则详解
    XFeat:速度精度远超superpoint的轻量级图像匹配算法
  • 原文地址:https://blog.csdn.net/u011386173/article/details/127852678