区间和的个数.(LeetCode原题第327题)
给你一个整数数组 nums 以及两个整数 lower 和 upper 。求数组中,值位于范围 [lower, upper] (包含 lower 和 upper)之内的 区间和的个数 。
区间和 S(i, j) 表示在 nums 中,位置从 i 到 j 的元素之和,包含 i 和 j (i ≤ j)。
原题链接:https://leetcode.cn/problems/count-of-range-sum
具体演示案例,就不在举例了,可以打开链接去看。
解题思路
这个要求的是区间和的个数,我们这样解题:
1.首先把数组转换成前缀和数组,
2.根据前缀和数组,我们可以把问题转换为,[sum[i]-upper,sum[i]-lower]这个区间范围内的前缀和,(怎样理解这句话,假如,sum[17]就代表前17位数字和,减去sum[3],就表示4到17 的区间和,如果sum[3]在[sum[i]-upper,sum[i]-lower] 这个范围,那剩下的区间和就会满足在lower和upper之间。)
3.再用归并排序去分解成小任务去解决
这次我们先写主方法,把数组转换成前缀和数组
public static int merge(int[]arr,int lower,int upper){
if (arr == null || arr.length == 0){
return 0;
}
//前缀和数组
long[]sum = new long[arr.length];
sum[0] = arr[0]; //第一个元素的前缀和等于自身
//构造前缀和数组
for (int i = 1; i < arr.length;i++){
sum[i] = sum[i-1]+arr[i];
}
//递归方法时,我们这时要传入的是前缀和数组,已经不是之前的数组
return process(sum,0,arr.length-1,lower,upper);
}
递归方法
private static int process(long[] arr, int L, int R, int lower, int upper) {
//base case
if (L == R){
return arr[L] <= upper && arr[L] >= lower ? 1 : 0;
}
int M = L + ((R - L) >> 1);
//最后一行 每次执行的结果加上把两个数组合并时的数量就等于总数量
return process(arr,L,M,lower,upper)+
process(arr,M + 1,R,lower,upper)+
mergeSort(arr,L,M,R,lower,upper);
}
最后我们看合并的写法,也是归并排序的扩展.
private static int mergeSort(long[] arr, int l, int m, int r, int lower, int upper) {
//现在是计算合并时,右边前缀和,在左边有多少符合要求的.
int windowL = l; // 起步都是l
int windowR = l;
int ans = 0; //开始满足条件的个数为0
for (int i = m + 1;i <= r;i++){
//这个就是将问题转成成有多少满足在[sum[i]-upper,sum[i]-lower]这个区间范围内
long up = arr[i] - lower;
long lo = arr[i] - upper;
while(windowR <= m && arr[windowR] <= up){
//在范围内一直右移
windowR++;
}
while (windowL <= m && arr[windowL] < lo){
//不满足范围的话左边界一直左移
windowL++;
}
//计算满足条件的个数
ans += windowR - windowL;
}
//这个下面就是正常的归并排序的合并过程
int p1 = l;
int p2 = m + 1;
long[]help = new long[r - l + 1];
int i = 0;
while (p1 <= m && p2 <= r){
help[i++] = arr[p1]<=arr[p2] ? arr[p1++] : arr[p2++];
}
while (p1<=m){
help[i++] = arr[p1++];
}
while (p2<=r){
help[i++] = arr[p2++];
}
for (i = 0; i < help.length;i++){
arr[l+i] = help[i];
}
return ans;
}