给定一个整数数组 nums,返回区间和在 [lower, upper] 之间的个数,包含 lower 和 upper。
区间和 S(i, j) 表示在 nums 中,位置从 i 到 j 的元素之和,包含 i 和 j (i ≤ j)。
说明:
最直观的算法复杂度是 O(n2) ,请在此基础上优化你的算法。
示例:
输入: nums = [-2,5,-1], lower = -2, upper = 2,
输出: 3
解释: 3个区间分别是: [0,0], [2,2], [0,2],它们表示的和分别为: -2, -1, 2。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/count-of-range-sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
基本思路:利用multiset是有序数组,在每次插入元素之前,都要查询所有以该元素为为节点的符合条件的元素的数量
int countRangeSum(vector<int>& nums, int lower, int upper) {
int n= nums.size();
int64_t presum = 0;
multiset<int64_t> S;
S.insert(0); //致使下面的操作包括自身元素。
int ret = 0;
for(int i=0;i<n;i++){
presum += nums[i];
ret += distance(S.lower_bound(presum-upper),S.upper_bound(presum-lower));
S.insert(presum);
}
return ret;
}
基本思路:采用归并排序,每一次有两个有序数组,以左边数组里的元素为左边界,在右边数组中找到符合要求的最大元素数目
- 将前缀和数组分为两个有序数组,即左右分别有序数组,s1,s2
- 以s1[i],0<=i<s1.size(),为左边界,在s2上找符合要求的所有元素,
- 递归以上步骤。
vector<long> tmp;
int merge(vector<long> &sum,int l,int r,int &lower,int &upper){
if(r<=l)
return 0; //若前缀和数组从一开始,仅需要改变该处,针对单个元素,return sum[l]>=lower&&sum[l]<=upper;
//计算所有符合要求的自序列个数
int cnt=0;
int mid=(r-l)/2+l;
cnt+=merge(sum,l,mid,lower,upper);//保证右边的数组下标一定大于左边的数组
cnt+=merge(sum,mid+1,r,lower,upper);
int left_pos=l,right_lower=mid+1,right_upper=mid+1;
while(left_pos<=mid){
while(right_lower<=r&&sum[right_lower]-sum[left_pos]<lower)
right_lower++;
while(right_upper<=r&&sum[right_upper]-sum[left_pos]<=upper)
right_upper++;
cnt+=right_upper-right_lower;
left_pos++;
}
// 归并
int left=l;
int right=mid+1;
int pos=l;
while(left<=mid||right<=r){
if(left<=mid&&right<=r){
if(sum[left]<=sum[right]){
tmp[pos]=sum[left++];
}
else{
tmp[pos]=sum[right++];
}
}
else if(left>mid){
tmp[pos]=sum[right++];
}
else
tmp[pos]=sum[left++];
pos++;
}
for(int i=l;i<=r;i++){
sum[i]=tmp[i];
}
return cnt;
}
int countRangeSum(vector<int>& nums, int lower, int upper) {
vector<long> sum(nums.size()+1,0);
tmp.resize(nums.size()+1,0);
for(int i=0;i<nums.size();i++){
sum[i+1]=sum[i]+nums[i];
}
return merge(sum,0,nums.size(),lower,upper); //从零开始,保证包含数组单个元素自身。
}