题目描述
Given an integer array nums
, return the number of range sums that lie in [lower, upper]
inclusive.
Range sum S(i, j)
is defined as the sum of the elements in nums
between indices i
and j
(i
≤
j
), inclusive.
Note:
A naive algorithm of O(
n2
) is trivial. You MUST do better than that.
Example:
Given nums = [-2, 5, -1]
, lower = -2
, upper = 2
,
Return 3
.
The three ranges are : [0, 0]
, [2, 2]
, [0, 2]
and their respective sums are: -2
, -1
, 2
.
Credits:
Special thanks to @dietpepsi for adding this problem and creating all test cases.
Link:
https://leetcode.com/problems/count-of-range-sum/description/
解决方案
本题是求数组的区间和在给定范围内的个数。数组的区间和s(i,j)
指的是索引在i
与j
(i
≤
j
)之间的所有元素的和,给定范围在[lower,upper]
之间,求出所有满足lower
≤
s(i,j)
≤
upper
的区间[i,j]
的个数。
由之前类似的题目Range Sum Query - Immutable,Range Sum Query 2D - Immutable 和 Range Sum Query - Mutable,求区间和的问题一般是先计算累加和sums[i]
=nums[0]
+nums[1]
+ ··· +nums[i]
,则s(i,j)
=sums[j]
-sums[i]
。
通过参考资料,解决该题的基本思想是:
- 对累加和数组的某个元素
sums[i]
,对其之后(或之前)的元素进行排序。 - 对于有序序列sorted[start,end],找出满足
sorted[j]
≥lower
的第一个sorted[j]和满足sorted[k]
>upper
的第一个sorted[k]
,则k-j
即为数组中在[lower,upper]
范围内的所有元素的个数。
下面是两种解法,其中解法一被使用和引用的最多。
解法一
采用“归并排序(Merge Sort)”来求解,基本思路是将累加和数组sums[start,end]
分为左右两半,其中左半部[start,mid)
和右半部[mid,end)
先分别递归调用解法一求出各自的满足条件的区间和个数,并完成各自部分的排序。然后再将已经排好序的左右两部进行归并,完成整体排序。在整体排序过程中,遍历左半部分的sums[i]
,找出右半部分的sums[j]
和sums[k]
,使其满足:
sums[j]
是第一个满足sums[j]
− sums[i]
≥lower
的元素。sums[k]
是第一个满足sums[k]
−sums[i]
> upper
的元素。
则k-j
便是在[lower,upper]
之间的区间和的个数。因为采用自底向上的归并排序,所以保证了对每一个sums[i]
,能够找到其后所有满足条件的sums[j]
和sums[k]
。
在整体排序的过程中,还需要一个缓存数组cache
来存储每次遍历i
时的左右半排序结果,通过复制完成sums[start,end]
间的整体排序。
以下为源码:
//C++ Merge Sort
class Solution {
public:
int countRangeSum(vector<int>& nums, int lower, int upper) {
int len=nums.size();
vector<long long> sums(len+1,0);
for(int i=0;i<len;i++){
sums[i+1]=sums[i]+nums[i];
}
return countAndMergeSort(sums,0,len+1,lower,upper);
}
int countAndMergeSort(vector<long long>& sums,int start,int end,int lower,int upper){
if(end-start<=1)return 0;
vector<long long> cache(end-start);
int middle=(start+end)/2;
int cnt=countAndMergeSort(sums,start,middle,lower,upper)+
countAndMergeSort(sums,middle,end,lower,upper);
int j=middle,k=middle,t=middle;
int r=0;
for(int i=start;i<middle;++i){
while(j<end && sums[j]-sums[i]<lower)j++;
while(k<end && sums[k]-sums[i]<=upper)k++;
while(t<end && sums[t]<sums[i])cache[r++]=sums[t++];
cache[r++]=sums[i];
cnt+=k-j;
}
copy(cache.begin(),cache.begin()+r,sums.begin()+start);
return cnt;
}
};
解法二
使用multiset,遍历nums[i]
,逐一将i
位置的累加和插入到multiset中,保证已经插入的累加和有序。对于位置i
,multiset中已经有按序排列的前i
个累加和。目标是找到i
前的满足条件lower
≤
sums[i]
−
sums[j]
upper
的sums[j]
的个数,即寻找满足条件 sums[i]
−
upper
sums[j]
≤
sums[i]
−
<script type="math/tex" id="MathJax-Element-18">-</script> lower
的两个临界值之间的元素个数。临界值可以使用lower_bound
(返回有序集合中大于等于给定值的第一个数)和upper_bound
(返回有序集合中大于给定值的第一个数)来求得。
以下为源码:
//C++ multiset
class Solution {
public:
int countRangeSum(vector<int>& nums, int lower, int upper) {
multiset<long long> sums;
sums.insert(0);
int res=0;
long long sum=0;
for(int i=0;i<nums.size();i++){
sum+=nums[i];
res+=distance(sums.lower_bound(sum-upper),sums.upper_bound(sum-lower));
sums.insert(sum);
}
return res;
}
};
相关题目
LeetCode_303 Range Sum Query - Immutable
LeetCode_304 Range Sum Query 2D - Immutable
LeetCode_307 Range Sum Query - Mutable
参考资料
https://leetcode.com/problems/count-of-range-sum/discuss/
https://www.cnblogs.com/grandyang/p/5162678.html