给定一个整数数组 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)
题目分析
①.求区间和,一般先构建前缀和数组进行数据预处理,可以 O(1) 得到任意区间和。
②.查找元素,一般先进行排序,利用单调性可以快速的缩减查找范围,减少时间复杂度。
③.归并排序包含了两个过程:从上往下的分解:把当前区间一分为二,直至分解为若干个长度为1的子数组;从下往上的合并:两个有序的子区域两两向上合并。
④.对于本题,归并过程中,右区间中元素分别和左区间中元素的差即原数组的各区间和,利用有序可减少重复的比较操作。
代码示例
class Solution {
public:
int countRangeSum(vector<int>& nums, int lower, int upper) {
int size = nums.size();
if( size == 0 ) return 0;
vector<long long > presum;//前缀和数组, [0,i] 区间和
long long pre = 0;
for( auto & num : nums)
{
pre += num;
presum.emplace_back(pre);
}
int result = 0;
vector<long long > temp(size);//用于合并两个有序数组的临时数组
mergesort(presum,lower,upper,temp,0,size-1,result);
return result;
}
void mergesort(vector<long long>& presum, int lower, int upper,vector<long long >& temp,int left,int right,int &result)
{
if( left == right)//分到只剩一个元素
{
if( presum[left] >= lower && presum[left] <= upper)
{
result++;
}
return;
}
int mid = left+(right-left)/2;
mergesort(presum,lower,upper,temp,left,mid,result);//使 [left,mid] 有序
mergesort(presum,lower,upper,temp,mid+1,right,result);//使 [mid+1,right] 有序
//合并之前先统计
int i = left ;// i 指向左区间
int j_left = mid+1;
int j_right = mid+1;// j_left、j_right 指向右区间,i < j,相减得到区间和
while( i < mid+1 )// i 固定时,j 越大差越大;j 固定时,i 越大差越小
{
while(j_left <= right && presum[j_left] - presum[i] < lower )//找到下限位置
{
j_left++;
}
j_right = j_left;
while( j_right <= right && presum[j_right] - presum[i] <= upper) //找到上限位置
{
j_right++;
result++;//找到一对
}
i++;
}
//合并
i = left;
int j = mid+1;
int t = 0;
while( i <= mid && j <= right)
{
if( presum[i] <= presum[j])
{
temp[t++] = presum[i++];
}
else
{
temp[t++] = presum[j++];
}
}
while( i <= mid )
{
temp[t++] = presum[i++];
}
while( j <= right )
{
temp[t++] = presum[j++];
}
t = 0;
i = left;
while( i <= right)
{
presum[i++] = temp[t++];
}
}
};