英雄算法7月25号
327
class Solution {
int lower, upper, ans = 0;
public:
int countRangeSum(vector<int>& nums, int lower, int upper) {
int len = nums.size();
this->lower = lower, this->upper = upper;
vector<long> prefix(len + 1, 0);
vector<long> tmp(len + 1, 0);
for(int i = 0; i < len; ++i) {
prefix[i + 1] = prefix[i] + nums[i];
}
mergeSort(prefix, tmp, 0, prefix.size() - 1);
return ans;
}
void mergeSort(vector<long>& prefix, vector<long>& tmp, int lef, int rig) {
if(lef < rig) {
int mid = (lef + rig) >> 1;
mergeSort(prefix, tmp, lef, mid);
mergeSort(prefix, tmp, mid + 1, rig);
//-------添加的核心操作begin------------
int L = mid + 1, R = mid + 1;
//遍历左区间的元素,在右区间寻找对应端点L,R。使得在L,R中的所有元素preifx[j] - prefix[i]都符合要求
for(int i = lef; i <= mid; ++i) {
//找到右区间 最左边的L
while(L <= rig && prefix[L] - prefix[i] < lower) ++L;
//找到右区间 最右边的R
while(R <= rig && prefix[R] - prefix[i] <= upper) ++R;
ans += R - L;
}
//-------添加的核心操作end---------------
merge(prefix, tmp, lef, rig);
}
}
void merge(vector<long>& prefix, vector<long>& tmp, int lef, int rig) {
int mid = (lef + rig) >> 1, L = lef, R = mid + 1, k = lef;
while(L <= mid && R <= rig) {
if(prefix[L] < prefix[R]) tmp[k++] = prefix[L++];
else tmp[k++] = prefix[R++];
}
while(L != mid + 1) tmp[k++] = prefix[L++];
while(R != rig + 1) tmp[k++] = prefix[R++];
for(int i = lef; i <= rig; ++i) prefix[i] = tmp[i];
}
};
思路
归并排序 + 前缀和
凡是涉及到区间和的,基本上都可以和前缀和扯上关系。本题中,前缀和数组是prefix。如果我们想要求出 i+ 1到 j 区间内的元素和, 我们可以这么计算
∑
k
=
i
+
1
j
n
u
m
s
[
k
]
=
p
r
e
f
i
x
[
j
]
−
p
r
e
f
i
x
[
i
]
\sum_{k = i+1}^{j}nums[k] = prefix[j]-prefix[i]
k=i+1∑jnums[k]=prefix[j]−prefix[i]
如果区间和满足以下条件
l
o
w
e
r
≤
p
r
e
f
i
x
[
j
]
−
p
r
e
f
i
x
[
i
]
≤
u
p
p
e
r
lower \leq prefix[j]-prefix[i] \leq upper
lower≤prefix[j]−prefix[i]≤upper
那么我们认为 这个区间和 满足要求,ans需要加1
至此,我们发现,如果想要求解原题答案,等价于求解在prefix数组中,有多少对满足条件的i,j
现在,我们运用归并排序求解,以下为例,解释在归并的基础上,添加的核心操作
tip:
- 以下操作均在merge之前
- 由于归并排序特性,数组左右两区间 均是升序
- 以下操作,做的事情是:
- 在 左区间 确定一个i
- 在 右区间 寻找 j 的范围(因为右区间是升序,所以只需要寻找一个区间 L,R。在此区间内的所有j 均满足prefix[j] - prefix[i] 在 [lower, upper]范围内 )
- 以下例子中,lower = -2,upper = 2
在本次merge之前,我们的prefix数组元素是 -2 0 2 3。我们枚举左区间元素,然后在 右区间 寻找j 的范围 [L ,R)。
part1
i = 0
枚举左区间第一个元素
1.1 确定L
我们先在 右区间 确定L
一开始,L指向2,发现prefix[L] - prefix[i] = 2 - (-2) = 4大于 lower。因为右区间是升序,所以L之后的所有元素 减去 prefix[i]都是大于lower的,因此L的位置被确定。
1.2 确定R
现在,我们在 右区间 确定R
一开始,R指向2,发现prefix[R] - prefix[i] = 2 - (-2) = 4大于upper。因为右区间是 升序,所以 R和R之后 的所有元素 减去 prefix[i] 都是严格大于upper,因此R的位置被确定
1.3 求解ans
现在,我们计算和 i 满足的 j 的个数。j 只要 被L,R夹在中间,都可以满足要求。
ans = R - L = 0
part2
i = 1
现在,我们枚举左区间第二个元素
2.1 求解L
依然是先求解L
我们发现,此时 prefix[L] - prefix[i] = 2 - 0 = 2 > lower,因为 右区间 是升序,因此 L之后的所有元素 减去 prefix[i] 均大于 lower,因此L的位置确定
2.2 求解R
现在,我们求解R
我们发现,prefix[R] - prefix[L] = 2 = upper。因为是等于upper,所以不能确定R的位置。如果R后面的元素也是2,那直接GG,因此,我们必须保证prefix[R] - prefix[i] 严格大于upper,直到R指向最后一个元素的下一位,也就是越界位置
2.3 继续求解R
我们继续求解R的位置
此时,我们发现 prefix[R] - prefix[L] = 3 - 0 > upper,因为 右区间是升序,因此 R和R右边的元素 减去 prefix[i] 都是大于 upper的。因此 R的位置被确定
2.4 求解ans
现在,我们计算和i 满足的 j 的个数。j 只要 被L,R夹在中间,都可以满足要求。
ans = R - L = 1
以上,就是全部计算过程
现在,我们回看整个过程:在merge前,我们枚举 左区间 元素,充当prefix[i], 在 右区间 寻找 j 的 范围L,R
这里,我们提出几个问题
- 为什么只枚举左区间的元素,用来充当prefix[i],而不枚举右区间元素?
- 为什么j 的范围只在 右区间寻找?
现在,我们给出解答
- 对于问题1:我们要注意,上述例子所展现的过程是归并的其中一个部分。不是说右区间不枚举元素,而是右区间已经枚举过了!对于右区间,又可以被拆分为 右区间的左区间, 右区间的右区间。在此之前,已经经过 枚举过了。如果我们在 上述例子中 时,枚举右区间, 那就重复了。
- 对于问题2:本质上和问题1是一致的。之所以只在右区间寻找j的范围,是因为j在左区间的情况,在此之前已经寻找过了。
解法
有了上述解释,代码也就不难书写。核心就是归并排序。在merge之前,我们需要 在左区间枚举i,右区间寻找j的L,R。计算有多少对 i,j符合要求
这里我们给出归并排序的代码
//因为归并练习的时候在学习Java,所以就用Java写了,没有C++版本,淦!
class Solution {
public int[] sortArray(int[] nums) {
int[] tmp = new int[nums.length];
mergeSort(nums, tmp, 0, nums.length - 1);
return tmp;
}
public void mergeSort(int[] nums, int[] tmp, int lef, int rig) {
if(lef < rig) {
int mid = (lef + rig) >> 1;
mergeSort(nums, tmp, lef, mid);
mergeSort(nums, tmp, mid + 1, rig);
merge(nums, tmp, lef, rig);
}
}
public void merge(int[] nums, int[] tmp, int lef, int rig) {
if(lef >= rig) return;
int mid = (lef + rig) >> 1, l = lef, r = mid + 1, k = lef;
while(l != mid + 1 && r != rig + 1) {
if(nums[l] < nums[r]) tmp[k++] = nums[l++];
else tmp[k++] = nums[r++];
}
while(l != mid + 1) tmp[k++] = nums[l++];
while(r != rig + 1) tmp[k++] = nums[r++];
for(int i = lef; i <= rig; ++i) nums[i] = tmp[i];
}
}