LeetCode第 327 题:区间和的个数(C++)

327. 区间和的个数 - 力扣(LeetCode)

暴力法

简单且超时58。。。

class Solution {
public:
    int countRangeSum(vector<int>& nums, int lower, int upper) {
        int count = 0;
        for(int i = 0; i < nums.size(); ++i){
            if(nums[i] <= upper && nums[i] >= lower)    ++count;
            long sum = nums[i];
            for(int j = i+1; j < nums.size(); ++j){
                sum += nums[j];
                if(sum <= upper && sum >= lower)    ++count;
            }
        }
        return count;
    }
};

那就想想怎么优化吧。一说到优化,可以存储前缀和:

class Solution {
public:
    int countRangeSum(vector<int>& nums, int lower, int upper) {
        int n= nums.size();
        long presum = 0;
        vector<long> S(n+1,0);
        int ret = 0;
        for(int i=1;i<=n;i++){
            presum += nums[i-1];
            for(int j=1;j<=i;j++){
                if(lower <= presum - S[j-1] && presum - S[j-1] <= upper) ret++;
            } 
            S[i] = presum;
        }
        return ret;
    }        
};

其实复杂度一样,只是采取了前缀和的思路来计算区间和,也是超时的。

归并

先考虑前缀和,举个例子:

nums = [4,2,3,7,-1,1,5],lower = 0, upper = 15
前缀和数组为:
temp = [4,6,9,16,15,16,21]
我们可以利用前缀和之间的差值来表示区间和:
temp[4] - temp[1] = 15 - 6 = 9
9 = nums[2] + nums[3] + nums[4]
也就是说,有了前缀和数组,我们想要计算某个区间的和是否符合要求,就不用进行累加了,直接利用区间边界对应的前缀和进行相减就行。

参考:327.区间和的个数题解综合 - 区间和的个数 - 力扣(LeetCode)

除了这一题,归并的思路也还常用在求逆序对的题目里面。

class Solution {
public:
    int lower, upper, res;
    long tmp[10000];//归并排序辅助数组
    void merge(vector<long> &a, int l, int mid, int r){//就是归并的merge
        int i = l, j = mid+1, k = 0;
        while(i <= mid && j <= r){
            if(a[i] <= a[j])    tmp[k++] = a[i++];
            else tmp[k++] = a[j++];
        }
        while(i <= mid) tmp[k++] = a[i++];
        while(j <= r) tmp[k++] = a[j++];
        copy(tmp, tmp+k, a.begin()+l);
    }
    void merge_sort(vector<long> &a, int l, int r){
        if(l >= r)  return;
        int mid = l + (r-l)/2;
        merge_sort(a, l, mid);
        merge_sort(a, mid+1, r);
        //以因为此时处理的两部分已经是有序的
        //k,j放在外面定义,注意这个细节,因为k,j不用每次都都从头开始查询
        //放在for循环内部会超时,因为每次都会从头开始查询
        int k = mid+1, j = k;
        for(int i = l; i <= mid; ++i){//对于左边部分的每一个元素
            while(k <= r && a[k] - a[i] < lower)    ++k;//区间和 < lower
            while(j <= r && a[j] - a[i] <= upper)   ++j;//区间和 <= 上界
            res += j - k;//累加区间个数
        }
        if(a[mid] <= a[mid+1])  return;//提前终止,此时已经有序了
        merge(a, l, mid, r);
    }
    int countRangeSum(vector<int>& nums, int lower, int upper) {
        this->lower = lower;
        this->upper = upper;
        int n = nums.size();
        vector<long> presum(n+1, 0);//前缀和数组,首部多一个0相当于哨兵
        for(int i = 0; i < n; ++i)  presum[i+1] = presum[i] + nums[i];
        merge_sort(presum, 0, n);
        return res;      
    }
};

可以使用函数inplace_merge简化:

class Solution {
public:
    int lower, upper, res;
    void merge_sort(vector<long> &nums, int l, int r){//注意这儿的r是不包含的
        if(r-l <= 1)  return;
        int mid = l + (r-l)/2;
        merge_sort(nums, l, mid);//左闭右开区间
        merge_sort(nums, mid, r);
        int j = mid, k = j;
        for(int i = l; i < mid; ++i){//对于左边部分的每一个元素
            while(k < r && nums[k] - nums[i] < lower)    ++k;//区间和 < lower
            while(j < r && nums[j] - nums[i] <= upper)   ++j;//区间和 <= 上界
            res += j - k;//累加区间个数
        }
        if(nums[mid-1] <= nums[mid])  return;//提前终止,此时已经有序了
        inplace_merge(nums.begin()+l, nums.begin()+mid, nums.begin()+r);
    }
    int countRangeSum(vector<int>& nums, int lower, int upper) {
        this->lower = lower;
        this->upper = upper;
        int n = nums.size();
        vector<long> presum(n+1, 0);//前缀和数组,首部多一个0相当于哨兵
        for(int i = 0; i < n; ++i)  presum[i+1] = presum[i] + nums[i];
        merge_sort(presum, 0, presum.size());
        return res;      
    }
};

平衡树

这个思路很好,借助平衡树进行排序,刚好平衡树还能二分查找,又可以使用distance函数计算距离。

class Solution {
public:
    int countRangeSum(vector<int>& nums, int lower, int upper) {      
        int n= nums.size();
        long presum = 0;
        //本质是平衡二叉查找树(红黑树)
        multiset<long> S;//因为有重复元素,而且前缀和并不有序,使用multiset存储并排序
        S.insert(0);
        int ret = 0;
        //区间和转化为前缀和的相减
        for(int i=0;i<n;i++){
            presum += nums[i];//一边插入一边查找
            //不过对于红黑树而言,distance函数是O(n)的
            //当然distance函数对于红黑树来说,可以简单地一直递增迭代器得到
            ret += distance(S.lower_bound(presum-upper), S.upper_bound(presum-lower));//二分查找上下界
            S.insert(presum);
        }
        return ret;
    }        
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值