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

news/2024/5/20 6:44:50 标签: 滑动窗口, 算法

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 <iostream>
#include <vector>
#include <deque>
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;
}
  • 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("测试结束");

	}
}

http://www.niftyadmin.cn/n/5721.html

相关文章

STL浅析

最近在敲代码的时候&#xff0c;遇到了一些STL知识&#xff0c;感觉比较杂乱&#xff0c;所以在此利用已有资料&#xff0c;综合一下自己的理解发布本篇博客。 C STL之动态数组vector&#xff08;⽮量&#xff09; C语⾔⾥⾯⽤ int arr[] 定义数组&#xff0c;它的缺点是数组…

详细讲解网络协议:TCP和UDP什么区别?

该文章是学习了 B 站 up 主的视频做的总结&#xff0c;讲的很通俗易懂&#xff0c;首先感谢博主的分享。视频地址&#xff1a;https://www.bilibili.com/video/BV1kV411j7hA/?spm_id_from333.337.search-card.all.click&vd_source0a3d4c746a63d737330e738fa043eaf6 重新认…

一站式工业边缘数据采集处理与设备反控实践

对接繁杂多样的工业协议、对海量设备产生的生产数据进行采集和处理一直是工业领域智能化推进的难点。EMQ 通过提供边缘工业协议网关软件 Neuron 和边缘流式处理引擎 eKuiper&#xff0c;分别解决了边缘侧设备数据的采集与处理。 之前&#xff0c;要想实现两个产品的协同工作&a…

零入门容器云网络-3:基于SNAT技术使得veth-pair链接的网络可以访问本局域网的其他节点

已发表的技术专栏&#xff08;订阅即可观看所有专栏&#xff09; 0  grpc-go、protobuf、multus-cni 技术专栏 总入口 1  grpc-go 源码剖析与实战  文章目录 2  Protobuf介绍与实战 图文专栏  文章目录 3  multus-cni   文章目录(k8s多网络实现方案) 4  gr…

js 对象深拷贝递归实现

const obj {name:changjk,family:{father:zs,mother:shh},hobby:[打游戏,喝茶,打羽毛球]} //深拷贝 const cloneObj(obj)>{ const newObj{} for(const key in obj){ if(Object.prototype.toString.call(obj[key]) [Object,Array]){ cloneObj(obj[key]) }else if(Object.pr…

原来我们一直写的是违反面向对象编程风格的代码

众所周知&#xff0c;很多业务系统都是基于 MVC 三层架构来开发的&#xff0c;虽然这种开发模式已经成为标准的 Web 项目的开发模式&#xff0c;但它却违反了面向对象编程风格&#xff0c;是一种彻彻底底的面向过程的编程风格&#xff0c;因此被有些人称为反模式&#xff08;an…

对象的比较(下)

作者&#xff1a;~小明学编程 文章专栏&#xff1a;Java数据结构 格言&#xff1a;目之所及皆为回忆&#xff0c;心之所想皆为过往 目录 比较器的比较 equals的比较 三种方式的比较 比较器的比较 class Card {public int rank; // 数值public String suit; // 花色public…

04 C++循环控制-课后巩固实训

第1关:(控制计数器)打印钻石 任务描述 本关任务:输入为一个3~19之间的一个奇数n, 输出钻石图案,如下图所示,若输入不符合要求,则退出。 编程要求 根据提示,在右侧编辑器补充代码,在输入不符合要求时输出 wrong input!。 测试说明 平台会对你编写的代码进行测试。 …