暴力法
简单且超时58。。。
class Solution {
public:
int countRangeSum(vector<int>& nums, int lower, int upper) {
int count = 0;
for(int i = 0; i < nums.size(); ++i){
if(nums[i] <= upper && nums[i] >= lower) ++count;
long sum = nums[i];
for(int j = i+1; j < nums.size(); ++j){
sum += nums[j];
if(sum <= upper && sum >= lower) ++count;
}
}
return count;
}
};
那就想想怎么优化吧。一说到优化,可以存储前缀和:
class Solution {
public:
int countRangeSum(vector<int>& nums, int lower, int upper) {
int n= nums.size();
long presum = 0;
vector<long> S(n+1,0);
int ret = 0;
for(int i=1;i<=n;i++){
presum += nums[i-1];
for(int j=1;j<=i;j++){
if(lower <= presum - S[j-1] && presum - S[j-1] <= upper) ret++;
}
S[i] = presum;
}
return ret;
}
};
其实复杂度一样,只是采取了前缀和的思路来计算区间和,也是超时的。
归并
先考虑前缀和,举个例子:
nums = [4,2,3,7,-1,1,5],lower = 0, upper = 15
前缀和数组为:
temp = [4,6,9,16,15,16,21]
我们可以利用前缀和之间的差值来表示区间和:
temp[4] - temp[1] = 15 - 6 = 9
9 = nums[2] + nums[3] + nums[4]
也就是说,有了前缀和数组,我们想要计算某个区间的和是否符合要求,就不用进行累加了,直接利用区间边界对应的前缀和进行相减就行。
参考:327.区间和的个数题解综合 - 区间和的个数 - 力扣(LeetCode)
除了这一题,归并的思路也还常用在求逆序对的题目里面。
class Solution {
public:
int lower, upper, res;
long tmp[10000];//归并排序辅助数组
void merge(vector<long> &a, int l, int mid, int r){//就是归并的merge
int i = l, j = mid+1, k = 0;
while(i <= mid && j <= r){
if(a[i] <= a[j]) tmp[k++] = a[i++];
else tmp[k++] = a[j++];
}
while(i <= mid) tmp[k++] = a[i++];
while(j <= r) tmp[k++] = a[j++];
copy(tmp, tmp+k, a.begin()+l);
}
void merge_sort(vector<long> &a, int l, int r){
if(l >= r) return;
int mid = l + (r-l)/2;
merge_sort(a, l, mid);
merge_sort(a, mid+1, r);
//以因为此时处理的两部分已经是有序的
//k,j放在外面定义,注意这个细节,因为k,j不用每次都都从头开始查询
//放在for循环内部会超时,因为每次都会从头开始查询
int k = mid+1, j = k;
for(int i = l; i <= mid; ++i){//对于左边部分的每一个元素
while(k <= r && a[k] - a[i] < lower) ++k;//区间和 < lower
while(j <= r && a[j] - a[i] <= upper) ++j;//区间和 <= 上界
res += j - k;//累加区间个数
}
if(a[mid] <= a[mid+1]) return;//提前终止,此时已经有序了
merge(a, l, mid, r);
}
int countRangeSum(vector<int>& nums, int lower, int upper) {
this->lower = lower;
this->upper = upper;
int n = nums.size();
vector<long> presum(n+1, 0);//前缀和数组,首部多一个0相当于哨兵
for(int i = 0; i < n; ++i) presum[i+1] = presum[i] + nums[i];
merge_sort(presum, 0, n);
return res;
}
};
可以使用函数inplace_merge
简化:
class Solution {
public:
int lower, upper, res;
void merge_sort(vector<long> &nums, int l, int r){//注意这儿的r是不包含的
if(r-l <= 1) return;
int mid = l + (r-l)/2;
merge_sort(nums, l, mid);//左闭右开区间
merge_sort(nums, mid, r);
int j = mid, k = j;
for(int i = l; i < mid; ++i){//对于左边部分的每一个元素
while(k < r && nums[k] - nums[i] < lower) ++k;//区间和 < lower
while(j < r && nums[j] - nums[i] <= upper) ++j;//区间和 <= 上界
res += j - k;//累加区间个数
}
if(nums[mid-1] <= nums[mid]) return;//提前终止,此时已经有序了
inplace_merge(nums.begin()+l, nums.begin()+mid, nums.begin()+r);
}
int countRangeSum(vector<int>& nums, int lower, int upper) {
this->lower = lower;
this->upper = upper;
int n = nums.size();
vector<long> presum(n+1, 0);//前缀和数组,首部多一个0相当于哨兵
for(int i = 0; i < n; ++i) presum[i+1] = presum[i] + nums[i];
merge_sort(presum, 0, presum.size());
return res;
}
};
平衡树
这个思路很好,借助平衡树进行排序,刚好平衡树还能二分查找,又可以使用distance函数计算距离。
class Solution {
public:
int countRangeSum(vector<int>& nums, int lower, int upper) {
int n= nums.size();
long presum = 0;
//本质是平衡二叉查找树(红黑树)
multiset<long> S;//因为有重复元素,而且前缀和并不有序,使用multiset存储并排序
S.insert(0);
int ret = 0;
//区间和转化为前缀和的相减
for(int i=0;i<n;i++){
presum += nums[i];//一边插入一边查找
//不过对于红黑树而言,distance函数是O(n)的
//当然distance函数对于红黑树来说,可以简单地一直递增迭代器得到
ret += distance(S.lower_bound(presum-upper), S.upper_bound(presum-lower));//二分查找上下界
S.insert(presum);
}
return ret;
}
};