题目
给你一个整数数组 nums 以及两个整数 lower 和 upper 。求数组中,值位于范围 [lower, upper] (包含 lower 和 upper)之内的区间和的个数 。
区间和 S(i, j) 表示在 nums 中,位置从 i 到 j 的元素之和,包含 i 和 j (i ≤ j)。
分析
- 如果用最暴力的方式就是有多少个子数组都遍历出来,那么将从0 ~ 0,0 ~ 1 ,0 ~ 2,0 ~ N - 1,1 ~ 1 ,1 ~ 2,1 ~ N -1的方式进行遍历,看哪个范围子数组的累加和在【lower,upper】上,此时的时间复杂度是 O ( N 2 ) O(N^2) O(N2) ,那对于每一个从(i,j)的子数组,验证的方式是遍历的话,那整体的时间复杂度就是 O ( N 3 ) O(N^3) O(N3)
- 如果能够避免遍历,根据原始数组生成一个前缀和数组的话,每次验证【i - j】范围的上的数的话,就可以直接对前缀和数组进行操作,时间复杂度就可以从 O ( N 3 ) O(N^3) O(N3)到 O ( N 2 ) O(N^2) O(N2)
转化
- 看原数组中已每一个数结尾的子数组(0 - 0 ,0 - 1,1 - 1)是否在【lower,upper】上,而后将求得的数进行累加,就是整个的答案。
- 如果原始数组中,【i,j】范围的累加和在 【lower,upper】上,那么【0,j】减 【0,i -1】范围,也一定在【lower,upper】上。
- 所以,如果以X结尾的子数组的累加和是50,【lower,upper】的范围是【10,20】,那么就看X之前的子数组有哪一个范围是在【30,40】的,如果有,则一定有子数组的累加和范围在【lower,upper】上。
举例
假设,0 - 10位置结尾的子数组,累加和是50, 给定的【lower,upper】的范围是【10,20】,那么用50 - upper,50 - lower就会得到一个范围是【30,40】,那如果0 -5位置的的子数组累加和是35,在【30,40】这个区间,就可以证明 6 - 10范围上一定存在累加和满足【10,20】区间的数组。
结果
生成前缀和数组,递归并merge,在merge过程中,从右组开始遍历,通过【X - upper,X - lower】每次都重新计算新的【lower,upper】,并判断左组有哪些数在这个心的区间,就代表有多少子数组符合原始的【lower,upper】区间。
代码实现:
public int countRangeSum(int[] nums, int lower, int upper) {
if (nums == null || nums.length == 0) {
return 0;
}
//生成前缀和数组
long[] sum = new long[nums.length];
sum[0] = nums[0];
for (int i = 1; i < nums.length; i++) {
sum[i] = sum[i - 1] + nums[i];
}
return count(sum,0,sum.length -1,lower,upper);
}
public static int count(long[] nums, int L, int R, int lower, int upper) {
// L == R,则看在原始数组中,0 - L范围累加和是否满足在lower 和 upper区间。
if (L == R) {
return lower <= nums[L] && nums[L] <= upper ? 1 : 0;
}
int mid = L + ((R - L) >> 1);
return count(nums, L, mid, lower, upper) +
count(nums, mid + 1, R, lower, upper) +
merge(nums, L, mid, R, lower, upper);
}
public static int merge(long[] nums, int L, int M, int R, int lower, int upper) {
int windowL = L;
int windowR = L;
int ans = 0;
//从右组遍历
for (int i = M + 1; i <= R; i++) {
//
long max = nums[i] - lower;
long min = nums[i] - upper;
while (windowL <= M && nums[windowL] < min){
windowL++;
}
while (windowR <=M && nums[windowR] <= max){
windowR++;
}
ans += windowR - windowL;
}
int p1 = L;
int p2 = M + 1;
long[] help = new long[R - L + 1];
int index = 0;
while (p1 <= M && p2 <= R) {
help[index++] = nums[p1] <= nums[p2] ? nums[p1++] : nums[p2++];
}
while (p1 <= M) {
help[index++] = nums[p1++];
}
while (p2 <= R) {
help[index++] = nums[p2++];
}
for (int j = 0; j < help.length; j++) {
nums[L + j] = help[j];
}
return ans;
}