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:
    vector<long> tmp;//归并排序辅助数组
    void merge(vector<long> &S,int left, int mid, int right){//归并排序标准merge函数了
        int i = left, j = mid + 1;
        int k = 0;
        while(i <= mid && j <= right){
            if(S[i] <= S[j])  tmp[k++] = S[i++];
            else    tmp[k++] = S[j++];
        }
        while(i <= mid) tmp[k++] = S[i++];
        while(j <= right) tmp[k++] = S[j++];
        copy(tmp.begin(), tmp.begin() + k, S.begin() + left);//使用标准库函数好像更快一点
    }

    int merge_sort(vector<long> &S,int left,int right,int lower,int upper){
        if(left >= right) return 0;
        int cnt = 0;
        int mid = left + (right-left)/2;
        cnt += merge_sort(S, left, mid, lower, upper);
        cnt += merge_sort(S, mid+1, right, lower, upper);
        //计算区间和符合要求的个数
        int i = left;
        int j = mid + 1, k = mid + 1;
        while(i <= mid){//对左半部分的每个元素
            //auto it1 = lower_bound(S.begin()+mid+1, S.begin() + right+1, S[i] + lower);
            //auto it2 = upper_bound(S.begin()+mid+1, S.begin() + right+1, S[i] + upper);
            while(k <= right && S[k] - S[i] < lower)k++;//区间和 < lower
            while(j <= right && S[j] - S[i] <= upper)j++;//区间和 <= 上界
            cnt += j - k;
            //cnt += it2 - it1;
            ++i;
        }
        if(S[mid] <= S[mid+1])    return cnt; //加一个判断,此时数组已经是有序的,不用进行后续操作了
        merge(S, left, mid, right);
        return cnt;
    }

    int countRangeSum(vector<int>& nums, int lower, int upper) {
        int n = nums.size();
        vector<long> S(n+1,0);//前缀和数组
        tmp = vector<long>(n+1);
        for(int i=1;i<=n;i++)   S[i] = S[i-1] + nums[i-1];
        return merge_sort(S, 0, n, lower, upper);
    }
};

平衡树

这个思路很好,借助平衡树进行排序,刚好平衡树还能二分查找,又可以使用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;
    }        
};

猜你喜欢

转载自blog.csdn.net/qq_32523711/article/details/107914989