区间和的个数

117 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

题目

327. 区间和的个数 - 力扣(LeetCode)

这个是力扣的连接,大家可以去上面测试一下,看看题目的介绍。

思路

这道题目的话,先要了解的是一个前缀和的概念

arr【1,2,6,4】

arr【2】的前缀和就是1+2+3

就是把前面的所有都加起来就可以了。

那么这道题问的是区间里面的数加起来有多少是在给定范围里面的。

那这个和前缀和有什么关系呢?

其实我们想一下,我们要计算arr【1】到arr【2】这个区间的总和,不就是arr【2】 - arr【0】吗?

这样换算之后我们只要遍历一遍数组,然后计算每一位的前缀和之后,就可以O(1)了解区间和。这个对于时间复杂度的提升是很大的。

那我们直到前缀和之后,我们又怎么做这道题目呢?

这道题目也是用到了归并的思想

不过在用归并思想之前,我们还要了解一个内容

假设我们要的区间是【2-6】

那么我们把前缀和先算出来【1,3,9,13】

我们这个时候怎么计算呢?

我们转换思路,是不是只要算出以这个位置为结尾的时候有多少个算出来,然后每个位置相加就可以了

根据这个思路我们看看怎么计算以这个位置为结尾,有多少个符合的对象。

我们假设此时要计算前缀和为9结尾,那我们要算9减去多少会在【2-6】里面,那不就是找一个【3-7】里面的一个数字吗?

相信看到这个转换一定很惊讶。

9 减去【3-7】里面随便一个数字,结果都是【2-6】里面的。那我们是不是转换了思路。

我们只要确定某个数,然后用这个数计算出一个范围,然后只要在前面的数字中找到有什么数字是符合这个范围的不就可以了吗?

那现在困扰我们的就是怎么找?

这个时候就要用出归并的思想了。

归并其实就是后面的数字和前面的数字比较的过程。

那我们怎么利用呢?

我们用举例来说明

举例

arr1【1,4,6】

arr2【3,7,9】

规定范围【2-9】

以上的这两个数组来自一个数组的左右,arr1在前面。

我们归并一下

我们先找到arr2【0】 = 3

那么我们要找的目标就是【-6 - 1 】

那我们用两个变量来确定区间 L,R

我们让L找到的地方小于-6

我们让R找到小于等于1的地方。

这个用两个变量找的时间复杂度是O(n)的,因为这两个变量是不回退的滑动窗。

有人会问为什么L是小于,R是小于等于。

这个就是很简单在这个数组里面【3,3,3,3,4】 找【3,5】范围内的数字,如果L是小于等于的话,会错过很多。

代码

我们从merge开始讲起

public static int merge(long[] sum , int l , int m , int r , int lower , int upper){
    int L = l;
    int R = l;
    int ans = 0;
    for(int i = m + 1;i <= r;i++){
        long max = sum[i] - lower;
        long min = sum[i] - upper;
        while(R <= m && sum[R] <= max){
            R++;
        }
        while(L <= m && sum[L] < min){
            L++;
        }
        ans += (R - L);
    }
    int cur1 = l;
    int cur2 = m + 1;
    long[] help = new long[r - l + 1];
    int index = 0;
    while(cur1 <= m || cur2 <= r){
        long num1 = cur1 <= m ? sum[cur1] : Long.MAX_VALUE;
        long num2 = cur2 <= r ? sum[cur2] : Long.MAX_VALUE;
        if(num1 < num2){
            help[index++] = sum[cur1++];
        }else{
            help[index++] = sum[cur2++];
        }
    }
    for(int i = 0;i< help.length;i++){
        sum[i + l] = help[i];
    }
    return ans;
}

第一个for循环就是用来计算有多少个达标的值,之后的循环就是普通的merge环境。

之后我们看看merge需要的分割成最小部分的函数吧

public static int process(long[] sum , int l , int r , int lower , int upper){
    if(l == r){
        return sum[l] >= lower && sum[l] <= upper ? 1 : 0;
    }
    int mid = l + ((r - l)/2);
    int left = process(sum , l , mid , lower , upper);
    int right = process(sum , mid + 1 , r , lower , upper);
    int cur = merge(sum , l , mid , r , lower , upper);
    return left + right + cur;
}

还有就是调用函数

public static int countRangeSum(int[] nums, int lower, int upper){
    long[] sum = new long[nums.length];
    sum[0] = nums[0];
    for(int i = 1;i<nums.length;i++){
        sum[i] = nums[i] + sum[i - 1];
    }
    return process(sum , 0 , sum.length - 1 , lower , upper);
}