327区间和的个数(前缀和+归并排序——困难)

1、题目描述

给定一个整数数组 nums 。区间和 S(i, j) 表示在 nums 中,位置从 i 到 j 的元素之和,包含 i 和 j (i ≤ j)。

请你以下标 i (0 <= i <= nums.length )为起点,元素个数逐次递增,计算子数组内的元素和。

当元素和落在范围 [lower, upper] (包含 lower 和 upper)之内时,记录子数组当前最末元素下标 j ,记作 有效 区间和 S(i, j) 。

求数组中,值位于范围 [lower, upper] (包含 lower 和 upper)之内的 有效 区间和的个数。

提示:0 <= nums.length <= 10^4

注意:最直观的算法复杂度是 O(n^2) ,请在此基础上优化你的算法。

2、示例

输入:nums = [-2,5,-1], lower = -2, upper = 2,
输出:3 
解释:
下标 i = 0 时,子数组 [-2]、[-2,5]、[-2,5,-1],对应元素和分别为 -2、3、2 ;其中 -2 和 2 落在范围 [lower = -2, upper = 2] 之间,因此记录有效区间和 S(0,0),S(0,2) 。
下标 i = 1 时,子数组 [5]、[5,-1] ,元素和 5、4 ;没有满足题意的有效区间和。
下标 i = 2 时,子数组 [-1] ,元素和 -1 ;记录有效区间和 S(2,2) 。
故,共有 3 个有效区间和。

3、题解

解法一:

基本思想:线段树,超时,时间复杂度O(n^2),建立线段树tree,然后双重循环分别遍历区间的起始和终止,利用线段树求出[i,j]区间的和sum,如果满足[lower,upper]区间res++

解法二:

基本思想:前缀和+归并排序,先计算得到nums的前缀和presum,presum[i+1]-presum[i]=nums[i],再对presum进行归并排序。

归并排序合并的两个数组过程中,比如nums1={2,5,8} nums2={1,7,9},遍历nums2中的每个元素减去nums1中的每个元素,就得到nums中所有范围区间的元素和,只要nums2每个元素-nums1每个元素的值在[lower,upper],res++,但是因为nums1和nums2都是有序的,所以对于当前nums1中的元素presum[i],只要presum[l]-presum[i]<lower得到nums2满足条件的左起点,presum[r]-presum[i]<=upper得到nums2满足条件的右终点,最后res+=r-l,大大优化,减少了很多遍历次数。

#include<vector>
#include<iostream>
#include<algorithm>
#include<unordered_map>
using namespace std;
class Solution {
public:
    int countRangeSum(vector<int>& nums, int lower, int upper) {
        //基本思想:线段树,超时,时间复杂度O(n^2)
        //建立线段树tree,然后双重循环分别遍历区间的起始和终止
        //利用线段树求出[i,j]区间的和sum,如果满足[lower,upper]区间res++
        int res=0;
        vector<long long> tree(nums.size()*2,0);
        for(int i=0;i<nums.size();i++)
            tree[nums.size()+i]=nums[i];
        for(int i=nums.size()-1;i>=1;i--)
            tree[i]=tree[2*i]+tree[2*i+1];
        for(int i=nums.size();i<tree.size();i++)
        {
            for(int j=i;j<tree.size();j++)
            {
                int left=i,right=j;
                long long sum=0;
                while(left<=right)
                {
                    if(left%2==1)
                    {
                        sum+=tree[left];
                        left++;
                    }
                    if(right%2==0)
                    {
                        sum+=tree[right];
                        right--;
                    }
                    left/=2;
                    right/=2;
                }
                if(sum>=lower && sum<=upper)
                    res++;
            }
        }
        return res;
    }
};
class Solution1 {
public:
    int res=0;
    vector<long> temp;  //归并排序辅助数组
    int countRangeSum(vector<int>& nums, int lower, int upper) {
        //基本思想:前缀和+归并排序,先计算得到nums的前缀和presum,presum[i+1]-presum[i]=nums[i],再对presum进行归并排序
        //归并排序合并的两个数组时,比如nums1={2,5,8} nums2={1,7,9}
        //遍历nums2中的每个元素减去nums1中的每个元素,就得到nums中所有范围区间的元素和
        //只要nums2每个元素-nums1每个元素的值在[lower,upper],res++
        //但是因为nums1和nums2都是有序的,所以对于当前nums1中的元素presum[i],只要presum[l]-presum[i]<lower得到nums2满足条件的左起点
        //presum[r]-presum[i]<=upper得到nums2满足条件的右终点,最后res+=r-l,大大优化,减少了很多遍历次数
        long sum=0;
        vector<long> presum(nums.size()+1,0);
        temp.resize(nums.size()+1,0);
        for(int i=0;i<nums.size();i++)
        {
            sum+=nums[i];
            presum[i+1]=sum;
        }
        mergeSort(presum,0,int(presum.size())-1,lower,upper);
        return res;
    }
    void mergeSort(vector<long>& presum, int left, int right, int lower, int upper)
    {
        if(left>=right)  return;
        int mid=(left+right)/2;
        mergeSort(presum,left,mid,lower,upper);
        mergeSort(presum,mid+1,right,lower,upper);
        Merge(presum,left,mid,right,lower,upper);
    }
    void Merge(vector<long>& presum, int left, int mid, int right, int lower, int upper)
    {
        int i=left,j=mid+1,k=left;
        int l=mid+1,r=mid+1;
        while(i<=mid)
        {
            while(l<=right&&presum[l]-presum[i]<lower)
                l++;
            while(r<=right&&presum[r]-presum[i]<=upper)
                r++;
            res+=r-l;
            i++;
        }
        i=left;
        j=mid+1;
        while(i<=mid&&j<=right)
        {
            if(presum[i]<presum[j])
                temp[k++]=presum[i++];
            else
                temp[k++]=presum[j++];
        }
        while(i<=mid)
            temp[k++]=presum[i++];
        while(j<=right)
            temp[k++]=presum[j++];
        for(int t=left;t<=right;t++)
            presum[t]=temp[t];
    }
};
int main()
{
    Solution1 solute;
    vector<int> nums={-2,5,-1};
    int lower=-2,upper=2;
    cout<<solute.countRangeSum(nums,lower,upper)<<endl;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值