题目:跳转至 327. 区间和的个数
给定一个整数数组 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:
int countRangeSum(vector<int>& nums, int lower, int upper) {
}
};
思路:
没看懂题目意思,直接看题解…官方解法一归并排序上面有讲解还是没看懂,继续翻题解。
懂了,以上面示例的例子分析,i,j皆为整数数组下标,区间和S(i,j)分别代表的区间及他们的和如下排列:
S(i,j) | 对应数组值 | 区间和 | 是否落在规定区间内 |
---|---|---|---|
S(0,0) | [-2] | -2 | √ |
S(0,1) | [-2,5] | 3 | |
S(0,2) | [-2,5,-1] | 2 | √ |
S(1,1) | [5] | 5 | |
S(1,2) | [5,-1] | 4 | |
S(2,2) | [2] | 2 | √ |
其中,落在[-2,2]中的只有3个区间,即结果为3。
看懂了题意但是题解我还是没搞懂。先跳过去在后半段把归并排序复习了一下。
class Solution {
public:
int countRangeSumRecursive(vector<long>& sum, int lower, int upper, int left, int right) {
//递归拆分待排序数列
if (left == right)
return 0;
else {
int mid = (left + right) / 2;
//左右边数组分别递归,结算各自区间上的前缀和对数
int n1 = countRangeSumRecursive(sum, lower, upper, left, mid);
int n2 = countRangeSumRecursive(sum, lower, upper, mid + 1, right);
int ret = n1 + n2;
// 首先统计下标对的数量,sum中每一个右边元素减去左边元素等于一段区间和
int i = left;
int l = mid + 1; //初始均指向右数组的起始节点
int r = mid + 1; //如果区间和小于lower,l右移,直到l指向的区间和大于等于lower,同理,r也是指向a第一个大于upper的位置
while (i <= mid) {
while (l <= right && sum[l] - sum[i] < lower) l++;
while (r <= right && sum[r] - sum[i] <= upper) r++;
ret += (r - l); //此时,[l...r)符合条件在[lower,upper]中
i++; //继续下一个左区域
}
//要做到上述结果,左右两数组必须有序,因此在每一次的双指针遍历后都应该将归并两个数组
vector<long> sorted(right - left + 1);
int p1 = left, p2 = mid + 1;
int p = 0;
while (p1 <= mid || p2 <= right) {
if (p1 > mid) {
sorted[p++] = sum[p2++];
} else if (p2 > right) {
sorted[p++] = sum[p1++];
} else {
if (sum[p1] < sum[p2]) {
sorted[p++] = sum[p1++];
} else {
sorted[p++] = sum[p2++];
}
}
}
//修改待排序数组
for (int i = 0; i < sorted.size(); i++) {
sum[left + i] = sorted[i];
}
return ret;
}
}
//转前缀和后不再对i,j位置敏感,只要排序后的前缀和数组中有一对前缀和满足sum[j]-sum[i]属于[lower,upper]即算一个序号对,i,j位置并不影响
int countRangeSum(vector<int>& nums, int lower, int upper) {
long s = 0;
vector<long> sum{0};
for(auto& v: nums) { //nums里面的值累加计算各自下标往前的前缀和
s += v;
sum.push_back(s);
}
//利用归并排序,并在合并时结算答案
return countRangeSumRecursive(sum, lower, upper, 0, sum.size() - 1);
}
};
复习一下归并排序:
合并是指把两个有序数组arr1和arr2按顺序放入一个更大的数组arr中,每次按下标比较两个数组对应的数据中的较小值(从小到大排列),依次存入arr中。
而要获取有序数组arr1和arr2,可以利用分治法,把原数组按下标从中分割,会出现左右两个数组,对获取到的数组继续对半切分,直至最后切分到的数组中只剩下一个数据值,单个数据自然有序。两两合并到只剩一个数组,归并排序就完成了。
//递归版
#include <iostream>
#include <vector>
using namespace std;
template<typename T>
void merge(vector<T>& arr,int left,int mid,int right){
//合并arr中下标left至mid,mid+1至right的部分
vector<T> temp(right-left+1); //定义合并后的数组
vector<T> leftarr(arr.begin()+left,arr.begin()+mid+1); //获取分割后的左侧数组,初始值为下标left到mid对应的数据
vector<T> rightarr(arr.begin()+mid+1,arr.begin()+right+1); //获取分割后的右侧数组,初始值为下标mid+1到right对应的数据
int i=0,j=0,k=left; //i,j分别指向分割后的两个数组的下标,k指向合并后的原数组(直接在原本数组上变动)
int leftsize=mid-left+1,rightsize=right-mid; //获取各自数组大小
while(i<leftsize && j<rightsize) //两个数组都还有值时
arr[k++]=leftarr[i]<rightarr[j]?leftarr[i++]:rightarr[j++]; //选出存入较小值
while(i<leftsize) //一个数组没有值后直接循环存入另一个数组内的有序值
arr[k++]=leftarr[i++];
while(j<rightsize)
arr[k++]=rightarr[j++];
}
template<typename T>
void mergeSort(vector<T>& arr,int left,int right){
if(left>=right) //结束条件,当只有一个数据时停止分割
return;
int mid=(left+right)/2;
mergeSort(arr,left,mid); //递归处理,把数组分割成左右两部分
mergeSort(arr,mid+1,right);
merge(arr,left,mid,right); //合并
}
int main()
{
vector<int> arr={1,8,7,8,0,3,9};
mergeSort(arr,0,arr.size()-1);
for(auto& x:arr)
cout<<x<<endl;
return 0;
}
//迭代版
#include <iostream>
#include <vector>
using namespace std;
template<typename T>
void mergeSort(vector<T>& arr,int len){
vector<T> temp(arr.begin(),arr.end()); //把arr的值赋给temp
for(int i=1;i<len;i*=2){ //逐级扩展,每次比较2,4,8...个
for(int start=0;start<len;start+=2*i){ //对每一个组内数据进行排序
int left=start; //定义组的左中右三个指针值,加上min防越界
int mid=min(start+i,len);
int right=min(start+2*i,len);
int k=left; //k为对应在原数组中的下标
int left1=left,right1=mid; //数组被分为左右两个数组,比较下标对应的值大小存在原数组上
int left2=mid,right2=right;
while(left1<right1 && left2<right2)
arr[k++]=temp[left1]<temp[left2]?temp[left1++]:temp[left2++];
while(left1<right1) //当一个数组值用完,另一个数组还有值时直接存放
arr[k++]=temp[left1++];
while(left2<right2)
arr[k++]=temp[left2++];
}
temp=arr; //重新赋值给temp
}
}
int main()
{
vector<int> arr={1,9,7,8,0,3,2};
mergeSort(arr,arr.size());
for(auto& x:arr)
cout<<x<<endl;
return 0;
}