问题描述:
给你一个整数数组 nums 以及两个整数 lower 和 upper 。求数组中,值位于范围 [lower, upper] (包含 lower 和 upper)之内的 区间和的个数 。
思路:
我们要求子数组累加和在[lower,upper]的个数,
普通方法就是
从0~0 0~1 0~2 ......0~n-1
1~1 1~2 ......1~n-1
..................
依次遍历,并且求出每个子数组的累加和然后看是否在区间中即可,
但是这样做的话时间复杂度是O(N^3)
那有没有更好的方式去求呢?
对于普通方法实现区间和问题引发的思考:
如果说累加和不需要我们计算直接可以通过一个下标去取得那么时间复杂度是不是可以降低一点呢?
如何直接得到某个数组的累加和呢?这个时候我们想到前缀和数组
如果说我用原数组构建前缀和数组的话,那么前缀和数组中的元素c 实际上就是原数组中0~c的累加和
那么我们假设arr中任意一个子数组为[a......,b] 其中a<b 那么怎么用前缀和数组来求这个数组的累加和呢?
答:preSum[b]-preSum[a-1] 就可以表示[a....b]子数组的累加和, 那么我们既然能够表示这个累加和,那么我们是不是可以拿preSum[b]-preSum[a-1]去与[lower,upper]去比较?
那么这时我们的问题就从原数组中找子数组,其本身累加和在[lower,upper]这个区间内的数组个数
变成了从前缀和数组中找第b个元素与第a-1个元素的差值与[lower,upper]的关系了
新的问题思考:
那么此时原方法中的求子数组的累加和从遍历的方式变成了常数操作,这个时候我们的时间复杂度肯定是降低了的
但是伴随着前缀和数组的出现,又多出了一个新的问题,也就是前缀和数组中第b个元素和第a-1个元素怎么求
它们之间有什么关系.
柳暗花明:
由于a,b都是子数组的上下界,所以a<b 所以a-1<b这是毋庸置疑的,
假设我们在前缀和数组中任给一个元素为b 那么a-1代表的就是元素b左边的元素,
那么既然要找前缀和数组中任意的一个元素b以及a-1的关系,那么实际上就是b元素和它左边的元素之间的关系
(因为b和a都是任意取的).
归并排序解决区间和问题
我们要求的是preSum[b]-preSum[a-1]与[lower,upper]之间的关系,此时preSum[b]=value
可以得出preSum[a-1] 要与[value-upper,value-lower]这个区间作比较
所以题意变成了:判断preSum[a-1]与区间[value-upper,value-lower]之间的关系
即左组中的元素与区间[value-upper,value-lower]之间的关系.
我们假定一个区间用来存放落在区间[value-upper,value-lower]之间的左组元素
并且区间下标为[newL,newR],但是为了减少额外空间复杂度的开销,我们并没有声明额外的辅助数组
(因为如果声明了额外的辅助数组的话我们对这个数组的加入弹出等操作也会使得时间复杂度变高.)
令newL=l;newR=l; 但凡左组中的元素满足题意我们就让newR++但凡左组的元素
不满足题意我们就让newL++
设 int ans为满足题意的子数组的个数
每次遍历完一个左组元素我们就让ans加上该元素所满足题意的个数(newR-newL)
遍历完左组后最后返回我们的答案ans即可.
代码实现
public class Code1_CountOfRangeSum {
public static int countRangeSum(int[] arr, int lower, int upper){
if(arr==null || arr.length==0){
return 0;
}
long[] preSum= new long[arr.length];
preSum[0]=arr[0];
for (int i = 1; i < arr.length; i++) {
preSum[i]=preSum[i-1]+arr[i];
}
return process(preSum,0,preSum.length-1,lower,upper);
}
public static int process(long[] preSum,int l,int r,int lower,int upper){
if(l==r){
return preSum[l] >= lower && preSum[l] <= upper ? 1 : 0;
}
int mid=l+((r-l)>>1);
return process(preSum, l, mid, lower, upper)+process(preSum, mid+1, r, lower, upper)+merge(preSum,l,mid,r,lower,upper);
}
public static int merge(long[] preSum,int l,int m,int r,int lower,int upper){
int ans=0;
int newL=l;
int newR=l;
for (int i = m+1; i <=r ; i++) {
long newLower= preSum[i]-upper;
long newUpper= preSum[i]-lower;
while(newL<=m && preSum[newL]<newLower){
newL++;
}
while(newR<=m && preSum[newR]<=newUpper){
newR++;
}
ans+=Math.max(0,newR-newL);
}
long[] help = new long[r - l + 1];
int i = 0;
int p1 = l;
int p2 = m + 1;
while (p1 <= m && p2 <= r) {
help[i++] = preSum[p1] <= preSum[p2] ? preSum[p1++] : preSum[p2++];
}
while (p1 <= m) {
help[i++] = preSum[p1++];
}
while (p2 <= r) {
help[i++] = preSum[p2++];
}
for (i = 0; i < help.length; i++) {
preSum[l + i] = help[i];
}
return ans;
}
}
最终结果