给定一个整数数组 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。
|
思路
暴力
计算全部区间,$O(n^2)$,超时。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class Solution { public: int countRangeSum(vector<int>& nums, int lower, int upper) { int n = nums.size(), res = 0; for(int i = 0;i < n;++i) { long long rangesum = 0L; for(int j = i;j < n;++j) { rangesum += nums[j]; if(rangesum >= lower && rangesum <= upper) ++res; } } return res; } };
|
分治
前缀和
计算前缀和presum
,问题转化为对任意的前缀和val = presum[i] - presum[j](i >= j)
落在 [lower, upper]
之间的个数。
计算前缀和注意第一个元素要补0。
若前缀和无序,计算的时间复杂度依旧是$O(n^2)$,若前缀和有序。计算时,可以使用双指针。
问题划分
若两个区间[l,m)
,[m, r)
的前缀和都是有序的,可以利用三指针。
p0
指向l
, p1
指向m
,p2
指向m
p1
后移直到presum[p1] - presum[p0] >= lower
p2
后移直到presum[p2] - presum[p0] > upper
p2 - p1
则为以p0
为起点的区间数量,由于[l, m)
是有序的,因此p0
后移之后,p1
,p2
必定后移,不用向前回溯,减少了时间。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| class Solution { public: int merage(vector<long long>& presum, int l, int r, int lower, int upper) { if(l == r - 1 || l == r) return 0; int m = l + (r - l)/2; int res = 0; res += merage(presum, l, m, lower, upper); res += merage(presum, m, r, lower, upper); int p0 = l, p1 = m, p2 = m; while(p0 < m) { while(p1 < r && presum[p1] - presum[p0] < lower) ++p1; while(p2 < r && presum[p2] - presum[p0] <= upper) ++p2; res += p2 - p1; ++p0; } vector<long long> temp(r - l); for(int i = 0, j = l, k = m;i < r - l;++i) { if(j < m && k < r) { if(presum[j] < presum[k]) temp[i] = presum[j++]; else temp[i] = presum[k++]; } else if(j < m){ temp[i] = presum[j++]; } else { temp[i] = presum[k++]; } } for(int i = 0;i < r - l;++i) { presum[i + l] = temp[i]; } return res; } int countRangeSum(vector<int>& nums, int lower, int upper) { int n = nums.size(); vector<long long> presum(n + 1); for(int i = 0;i < nums.size();++i) { presum[i+1] = presum[i] + nums[i]; } return merage(presum, 0, n + 1, lower, upper); } };
|
时间复杂度
$T(N) = 2*T(N/2) + O(N)$
$T(N) = O(NlogN)$
空间复杂度
设空间占用为 $M(N)$,递归调用所需空间为 $M(N/2)$,而合并数组所需空间为 O(N)O(N),故
$M(N) = O(N)$