区间和的个数

给你一个整数数组 nums 以及两个整数 lower 和 upper
求数组中,值位于范围 [lower, upper] (包含 lower 和 upper)之内的 区间和的个数

一. 前缀和+双重循环(超时)

class Solution {
public:
    int countRangeSum(vector<int>& nums, int lower, int upper) {
        int n = nums.size();
        vector<long long> presum(n+1);
        for(int i=0;i<n;i++)
            presum[i+1] = presum[i] + nums[i];
        int count = 0;//区间计数
        for(int i=0;i<n;i++){//区间起点
            if(nums[i]>=lower&&nums[i]<=upper) count++;//单个数区间
            for(int j=i+1;j<n;j++){//区间终点
                long long cursum = presum[j+1]-presum[i];
                if(cursum>=lower&&cursum<=upper) count++;
            }
        }
        return count;
    }
};

二. 前缀和+归并排序+双指针

对于有序的两子区间,可以用前缀和和双指针快速计算满足条件的个数

class Solution {
public:
    int countRangeSum(vector<int>& nums, int lower, int upper) {
        int n = nums.size();
        if(n==0) return 0;
        vector<long> presum(n+1);
        for(int i=0;i<n;i++)//计算前缀和
            presum[i+1] = presum[i] + nums[i];
        //后面只用看前缀和
        return countRange(presum,0,n,lower,upper);
    }
    int countRange(vector<long>&presum,int l,int r,int lower,int upper){
        if(l==r) return 0;//边界条件
        int mid = (l+r)/2;
        //分治归并
        return countRange(presum,l,mid,lower,upper)+
        countRange(presum,mid+1,r,lower,upper)+ 
        merge(presum,l,mid,r,lower,upper);
    }
    int merge(vector<long>&presum,int l,int m,int r,int lower,int upper){
        //对于两有序相邻子区间,计算下标对数量,有序是为了方便指针移动,减少查询
        int count = 0; 
        int i = l;//i在左区间初始指针
        int lp = m + 1;//右区间初始指针1
        int rp = m + 1;//右区间初始指针2
        while (i <= m) {//遍历左区间
            //找右区间满足条件的值,双指针
            while (lp <= r && presum[lp] - presum[i] < lower) lp++;//左闭
            while (rp <= r && presum[rp] - presum[i] <= upper) rp++;//右开
            count += (rp - lp);
            i++;
        }

        //合并有序数组
        int p1 = l; int p2=m+1; //合并区间辅助指针
        i=0;//辅助数组初始指针
        vector<long> sorted(r-l+1);//合并区间长度
        while(p1<=m&&p2<=r)
            sorted[i++] = presum[p1]<presum[p2]?presum[p1++]:presum[p2++];
        while(p1<=m) sorted[i++] = presum[p1++];
        while(p2<=r) sorted[i++] = presum[p2++];
        for(i=0;i<sorted.size();i++)
            presum[l+i] = sorted[i];
        
        return count;
    }
};

三 . 离散化树状数组


class Solution {
public:
    vector<int> tree;//树状数组
    int n;
    int countRangeSum(vector<int>& nums, int lower, int upper) {
        vector<long long> preSum(nums.size()+1);
        for(int i=0;i<nums.size();i++)//前缀和
            preSum[i+1] = preSum[i]+nums[i];
        
        //后面问题转化成了两点差在lower~upper间
        //即对于某一点,他的区域在presum[i]-upper到presum-lower之间,若其他点落在这里则说明满足条件
        set<long long> allNumbers;
        for (long long x: preSum) { //遍历前缀和,去重
            allNumbers.insert(x);
            allNumbers.insert(x - lower);
            allNumbers.insert(x - upper);
        }
        // 利用哈希表进行离散化
        unordered_map<long long, int> values; //用index离散化,只用表示相对位置
        int idx = 1;
        for (long long x: allNumbers) {
            values[x] = idx;
            idx++;
        }
        n = values.size();
        tree.resize(n+1);//把所有可能的位置建树,因为查询必须要点

        int ret = 0;
        for (int i = 0; i < preSum.size(); i++) { //遍历前缀和
            int left = values[preSum[i] - upper], right = values[preSum[i] - lower]; //查询指定区间的点的数目
            ret += sum(right) - sum(left-1);//进行查询
            update(values[preSum[i]], 1); //将坐标插入,单点更新
        }
        return ret;
    }

    int lowbit(int x){//求二进制化最后一位的值
        return x&(-x);
    }
    void update(int i,int k){ //在i位置加上k,O(logn)复杂度单点修改
        while(i<=n){//更新子树上所有值
            tree[i]+=k;
            i+=lowbit(i);//移动到父亲节点
        }
    }

    long long sum(int i){  //求数组前i项的和
        long long res=0;
        while(i>0){//O(logn)求前缀和
            res+=tree[i];
            i-=lowbit(i);//移动到前一棵子树(子区间)
        }
        return res;
    }
};

posted @ 2023-04-25 22:36  失控D大白兔  阅读(26)  评论(0编辑  收藏  举报