给定一个整数数组 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。
class Solution { public: //前缀和 sum[i...j]=pre[j]-pre[i] c++ set是有序的 int countRangeSum(vector<int>& nums, int lower, int upper) { vector<long> preSum; long sum = 0; for(int i=0;i<nums.size();i++){ sum += nums[i]; preSum.push_back(sum); } int res=0; multiset<long> pre; pre.insert(0); for(int i=0;i<preSum.size();i++){ // if(preSum[i]>= lower && preSum[i]<=upper){ // res++; // } // for(int j=i+1;j<preSum.size();j++){ // //这里找preSum[i]的过程导致两层循环。 // //lower <= preSum[j]-preSum[i] <= upper // // preSum[j]-upper =< preSum[i] <= preSum[j]-lower // //查找: preSum[i] 大于某个数,小于某个数的个数,若是有序数组,可以使用二分查找。 // // // // if(preSum[j]-preSum[i]>=lower && preSum[j]-preSum[i]<=upper ){ // // res++; // // } // } //遇到的每一个i,都找他之前的(0-i-1)个数中 位于(当前数-upper)=< preSum[i]<=(当前-lower) 之间得数的个数 //若是之前的0-i-1个数是有序的 //那么如果S数组是 有序的 我们就可以通过两次二分查找计算出有多少个x满足条件(d2-d1): //第一次二分查找找出第一个大于等于 presum - upper 的位置d1; //第二次二分查找找出第一个大于 presum - lower 的位置d2。 //如何快速保证每次(0-i-1)的前缀和数组都有序?将前(0-i-1)位置的数都存入multiset,利用红黑树 auto low = pre.lower_bound(preSum[i]-upper); auto high = pre.upper_bound(preSum[i]-lower); res += distance(low,high); pre.insert(preSum[i]); } return res; } };
二分查找
class Solution { public: //查找有序数组,第一个>=key(下限)的数 int find_first_position(vector<long> presum,long key) { if(presum.size() == 0) return 0; int low = 0, high = presum.size()-1; if(presum[high] < key) return high+1; while(low <= high){ int mid = low+(high-low)/2; if(presum[mid] < key) low = mid+1; else if(presum[mid] >= key) high = mid-1; } return low; } //查找有序数组,查找第一个可以插入到>=key(上限))的位置,即第一个大于key int find_last_position(vector<long> presum,long key) { if(presum.size()==0) return 0; int low = 0, high = presum.size()-1; if(presum[high] <= key) return high+1; while(low <= high){ int mid = low+(high-low)/2; if(presum[mid] <= key) low = mid+1; else if(presum[mid] > key) high = mid-1; } return low; } void SetToArray(multiset<long> pre,vector<long>& presum) { for(auto it= pre.begin();it!=pre.end();it++){ presum.push_back(*it); } return ; } //前缀和 sum[i...j]=pre[j]-pre[i] c++ set是有序的 int countRangeSum(vector<int>& nums, int lower, int upper) { vector<long> preSum; long sum = 0; for(int i=0;i<nums.size();i++){ sum += nums[i]; preSum.push_back(sum); } int res=0; multiset<long> pre; vector<long> presum; for(int i=0;i<preSum.size();i++){ //遇到的每一个i,都找他之前的(0-i-1)个数中 位于(当前数-upper)=< preSum[i]<=(当前-lower) 之间得数的个数 //若是之前的0-i-1个数是有序的 //那么如果S数组是 有序的 我们就可以通过两次二分查找计算出有多少个x满足条件(d2-d1): //第一次二分查找找出第一个大于等于 presum - upper 的位置d1; //第二次二分查找找出第一个大于 presum - lower 的位置d2。 //如何快速保证每次(0-i-1)的前缀和数组都有序?将前(0-i-1)位置的数都存入multiset,利用红黑树 if(preSum[i] <= upper && preSum[i] >= lower) res++; int lower_bound = find_first_position(presum,preSum[i]-upper); int upper_bound = find_last_position(presum,preSum[i]-lower); res += max(upper_bound-lower_bound,0); presum.clear(); pre.insert(preSum[i]); SetToArray(pre,presum); } return res; } };