前言
学习了归并排序,也要学习使用归并排序解决问题,先有力扣的一个困难难度的题327. 区间和的个数
题意解析
从小阅读理解也没得过高分,不过还是要说一下自己的理解。
原题描述
给你一个整数数组 nums
以及两个整数 lower
和 upper
。求数组中,值位于范围 [lower, upper]
(包含 lower
和 upper
)之内的 区间和的个数 。
区间和 S(i, j)
表示在 nums
中,位置从 i
到 j
的元素之和,包含 i
和 j
(i
≤ j
)。
解析题意
假定一个数组:
将这个数组拆分成各个小数组,小数组的累加和,满足 [lower, upper]
的累加和的个数,就是最终要的结果。
明晰思路
发现规律
暴力解析是个笨办法,但是往往这些笨办法中蕴含者一些规律:
- 拆分的数组个数,越来越少,第一次4个,第二次3个,第三次2个,第四次1个
- 以开头统计达标,和以结尾统计达标一样,上面是以开头统计,0–0,0–1,0–2,0–3,也可以以结尾统计,0–3,1–3,2–3,3–3.
这个规律有啥用,以开头统计的话,我不知道后面的累加上是否满足规则,以结尾统计的话,我用0–upper的累加和减去0–lower的累加和,不久得到lower-upper的累加和了?我这里提到的累加和不是真的是一个和,算法上叫前缀和,啥,咋又多出来个前缀和?这里简单解释一下吧,来个图解
回到原题,对这个数组求满足 [5, 10]
的累加和的个数,先看下以7结尾的满足[5,10]的有哪些,
- 下标==》0到7 减去 0到0 得到 1到7 :21-1=20
- 下标==》0到7 减去 0到1 得到 2到7 :21-3=20
- 下标==》0到7 减去 0到2 得到 3到7 :21-0=21
- 下标==》0到7 减去 0到3 得到 4到7 :21-5=16
- 下标==》0到7 减去 0到4 得到 5到7 :21-14=7
- 下标==》0到7 减去 0到5 得到 6到7 :21-14=7
- 下标==》0到7 减去 0到6 得到 7到7 :21-18=3
- 5到7 6到7 满足[5,10]
又有个规律出现了,以7结尾的数据的累加和是21,需要满足的累加和规则是[5,10],那我前缀和满足[21-10,21-5]==>[11,16],就得到满足[5,10]的统计了呗,图解如下:
代码思路
依赖前缀和实现,所以先搞一个方法实现前缀和,当前位置的前缀和就是上一位置的前缀和+这个位置的值
sum[i] = sum[i-1] + nums[i]
循环实现搞到前缀和数组sum。
我需要拆分数组啊,咋搞,之前学了拆分用递归,splitSorting(int[] arr, int l, int r)
本次还有规则范围判断呢,把规则范围也加进去,splitSorting(int[] arr, int l, int r,int lower,int upper)
递归完成的内容就是判断,子数组有多少个达标的。
伪代码:
splitSorting(int[] arr, int l, int r,int lower,int upper){
//考虑边界,弹出递归
if(l==r){
//装边界了,看看这个位置的前缀和是否在范围内,如果在计数1,否则不计数。
return arr[l]>l && arr[r] <r ? 1: 0;
}
int m = (l+r)/2;
int zuo = splitSorting(arr,l,m,lower,upper);
int you = splitSorting(arr,m+1,r,lower,upper);
//还需要判断是否达标的方法
int mer = merge(……);
return zuo + you + mer;
}
可是这也和上次的归并排序关系不大呀,归并排序主要采用分治排序的思路,怎么才能用上呢?
大神就是不一样,这是怎么想到的,merge方法将顺序排好了,那就意味着每个数的范围按照规则[当前和-10,当前和-5],必定是有序的😮
🤔,是升序的又有啥用呢?左组中的数在右组的新的范围中的数有几个。
代码实现
public static int countRangeSum(int[] nums, int lower, int upper) {
if (nums == null || nums.length == 0) {
return 0;
}
//前缀和
long[] sum = new long[nums.length];
//0--0前缀和就是自己
sum[0] = nums[0];
for (int i = 1; i < nums.length; i++) {
//上一次的累加和+本次的数,就是本次的累加和,放在i位置,作为i位置的前缀和
sum[i] = sum[i - 1] + nums[i];
}
return process(sum, 0, sum.length - 1, lower, upper);
}
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 M = L + ((R - L) >> 1);
return process(sum, L, M, lower, upper) + process(sum, M + 1, R, lower, upper)
+ merge(sum, L, M, R, lower, upper);
}
public static int merge(long[] arr, int L, int M, int R, int lower, int upper) {
int ans = 0;
int windowL = L;
int windowR = L;
// [windowL, windowR)
for (int i = M + 1; i <= R; i++) {
long min = arr[i] - upper;
long max = arr[i] - lower;
//以这个结尾的前缀和的规则变成了[min,max]
//左组中有多少个数满足这个新规则
while (windowR <= M && arr[windowR] <= max) {
windowR++;
}
while (windowL <= M && arr[windowL] < min) {
windowL++;
}
ans += windowR - windowL;
}
long[] help = new long[R - L + 1];
int i = 0;
int p1 = L;
int p2 = M + 1;
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;
}