LeetCode:327. 区间和的个数 逆序对 / multiset / 二分查找+二分插入

给定一个整数数组 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。

来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/count-of-range-sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

二分查找 / 插入

我们维护一个有序数组,表示当前的已知的区间和,然后每次向右推进右端点,我们得到一个新的前缀和,因为区间之和是 sum[r] - sum[l-1],目标变为:

  • 已知当前区间右端点前缀和 sr,找到所有左端点 sl,使得他们满足 lower<= sr - sl <=upper

那么有 sl 的范围:[sr-upper, sr-lower]
在已知的,升序存放sl值的数组中,二分查找:
找第一个 i 使得 sl大于等于 sr-upper
找第一个 j 使得 sl 大于 sr-lower
j-i 就是当前 sr(右端点)可以组成的逆序对个数

然后再把 sr 加入 sl 数组(插入位置也要二分搜索得到),准备为下一个sr做准备

很遗憾不能过
在这里插入图片描述

class Solution {
public:
    int countRangeSum(vector<int>& nums, int lower, int upper)
    {
        vector<long long> sumArr{0};
        long long ans=0, sum=0;
        for(int i=0; i<nums.size(); i++)
        {
            sum += nums[i];
            auto l = lower_bound(sumArr.begin(), sumArr.end(), sum-upper);
            auto r = upper_bound(sumArr.begin(), sumArr.end(), sum-lower);
            ans += r-l;
            auto x = lower_bound(sumArr.begin(), sumArr.end(), sum);
            sumArr.insert(x, sum);
        }
        return (int)ans;
    }
};

引入multiset优化,防止退化

因为最坏情况维护的二分查找数组,查找时可能会退化到O(n),而不是稳定O(log(n)),为了维护稳定的O(log(n)),我们需要使用**红黑树(multiset)**代替之前的有序数组,进行二分查找

终于AC了
在这里插入图片描述

class Solution {
public:
    int countRangeSum(vector<int>& nums, int lower, int upper)
    {
        multiset<long long> sumSet{0};
        long long ans=0, sum=0;
        for(int i=0; i<nums.size(); i++)
        {
            sum += nums[i];
            auto l = sumSet.lower_bound(sum-upper);
            auto r = sumSet.upper_bound(sum-lower);
            ans += distance(l, r);
            sumSet.insert(sum);
        }
        return (int)ans;
    }
};

正解:逆序对

其实就是求解前缀和数组 nums 中,找 i, j 使得 i < j 且满足lower<= nums[j] - nums[i] <= upper的有多少对,仔细一看,那就是有点像【逆序对问题啊】,这种找一对数,满足一定的大小关系(A[i] + bias < or > A[j])的

确定大小关系
即找nums[i] 满足 nums[j]-upper <= nums[i] <= nums[j]-lower

问题拆解为:
找nums[i1] 使得 nums[j]-upper <= nums[i1]
找nums[i2] 使得 nums[i2] <= nums[j]-lower

问题转化为:
如果将数组拆分两半,每一半都升序(归并递归调用后),在左边确定i1, i2

  • 第一个 i1 使得 nums[i1] < nums[j]-upper(因为递增,i1右边全都是 nums[i1] >= nums[j]-upper,合法的,i1 及其左边都是不合法的)
  • 第一个 i2 使得 nums[i2] <= nums[j]-lower (i2 及其左边都是合法的)

i2 - i1 就是满足两个边界的答案,如图证明:
在这里插入图片描述

注意
从大到小枚举 j 下标,因为要在升序序列中nums[i]< nums[j]的,如果从小到大枚举 j,因为 i 不走回头路,那么 nums[j] 变大可能导致之前 i 向前迭代而排除的一些nums[i]变得可用。

class Solution {
public:
    int mer(vector<long long>& nums, int l, int r, int lower, int upper)
    {
        if(l>=r || l<0 || r>=nums.size()) return 0;
        int mid=(l+r)/2, cnt=0, i1=mid, i2=mid;
        cnt += mer(nums, l, mid, lower, upper)+mer(nums, mid+1, r, lower, upper);
        for(int j=r; j>mid; j--)
        {
            while(i1>=l && nums[i1]>=nums[j]-upper) i1--;
            while(i2>=l && nums[i2]>nums[j]-lower) i2--;
            cnt += i2-i1;
        }
        inplace_merge(nums.begin()+l, nums.begin()+mid+1, nums.begin()+r+1);
        return cnt;
    }
    int countRangeSum(vector<int>& nums, int lower, int upper)
    {
        vector<long long> NUMS(nums.size()+1); NUMS[0]=0;
        for(int i=0; i<nums.size(); i++)
            NUMS[i+1]=nums[i]+NUMS[i];
        return mer(NUMS, 0, NUMS.size()-1, lower, upper);
    }
};
发布了262 篇原创文章 · 获赞 11 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_44176696/article/details/105092920