英雄算法7月25号

英雄算法7月25号

327

class Solution {
    int lower, upper, ans = 0;
public:
    int countRangeSum(vector<int>& nums, int lower, int upper) {
        int len = nums.size();
        this->lower = lower, this->upper = upper;
        vector<long> prefix(len + 1, 0);       
        vector<long> tmp(len + 1, 0);       
        for(int i = 0; i < len; ++i) {
            prefix[i + 1] = prefix[i] + nums[i];
        }
        mergeSort(prefix, tmp, 0, prefix.size() - 1);
        return ans;
    }
    void mergeSort(vector<long>& prefix, vector<long>& tmp, int lef, int rig) {
        if(lef < rig) {
            int mid = (lef + rig) >> 1;
            mergeSort(prefix, tmp, lef, mid);
            mergeSort(prefix, tmp, mid + 1, rig);
            //-------添加的核心操作begin------------
            int L = mid + 1, R = mid + 1;
            //遍历左区间的元素,在右区间寻找对应端点L,R。使得在L,R中的所有元素preifx[j] - prefix[i]都符合要求
            for(int i = lef; i <= mid; ++i) {
                //找到右区间 最左边的L
                while(L <= rig && prefix[L] - prefix[i] < lower) ++L;
                //找到右区间 最右边的R
                while(R <= rig && prefix[R] - prefix[i] <= upper) ++R;
                ans += R - L;
            }
            //-------添加的核心操作end---------------
            merge(prefix, tmp, lef, rig);
        }
    }
    void merge(vector<long>& prefix, vector<long>& tmp, int lef, int rig) {
        int mid = (lef + rig) >> 1, L = lef, R = mid + 1, k = lef;
        while(L <= mid && R <= rig) {
            if(prefix[L] < prefix[R]) tmp[k++] = prefix[L++];
            else tmp[k++] = prefix[R++];
        }
        while(L != mid + 1) tmp[k++] = prefix[L++];
        while(R != rig + 1) tmp[k++] = prefix[R++];
        for(int i = lef; i <= rig; ++i) prefix[i] = tmp[i];
    }
};

思路

归并排序 + 前缀和

凡是涉及到区间和的,基本上都可以和前缀和扯上关系。本题中,前缀和数组是prefix。如果我们想要求出 i+ 1到 j 区间内的元素和, 我们可以这么计算
∑ k = i + 1 j n u m s [ k ] = p r e f i x [ j ] − p r e f i x [ i ] \sum_{k = i+1}^{j}nums[k] = prefix[j]-prefix[i] k=i+1jnums[k]=prefix[j]prefix[i]
如果区间和满足以下条件
l o w e r ≤ p r e f i x [ j ] − p r e f i x [ i ] ≤ u p p e r lower \leq prefix[j]-prefix[i] \leq upper lowerprefix[j]prefix[i]upper
那么我们认为 这个区间和 满足要求,ans需要加1

至此,我们发现,如果想要求解原题答案,等价于求解在prefix数组中,有多少对满足条件的i,j

现在,我们运用归并排序求解,以下为例,解释在归并的基础上,添加的核心操作


tip:

  • 以下操作均在merge之前
  • 由于归并排序特性,数组左右两区间 均是升序
  • 以下操作,做的事情是:
    • 左区间 确定一个i
    • 右区间 寻找 j 的范围(因为右区间是升序,所以只需要寻找一个区间 L,R。在此区间内的所有j 均满足prefix[j] - prefix[i] 在 [lower, upper]范围内 )
  • 以下例子中,lower = -2,upper = 2

在本次merge之前,我们的prefix数组元素是 -2 0 2 3。我们枚举左区间元素,然后在 右区间 寻找j 的范围 [L ,R)。

part1

i = 0

枚举左区间第一个元素


1.1 确定L

请添加图片描述

我们先在 右区间 确定L

一开始,L指向2,发现prefix[L] - prefix[i] = 2 - (-2) = 4大于 lower。因为右区间是升序,所以L之后的所有元素 减去 prefix[i]都是大于lower的,因此L的位置被确定


1.2 确定R

请添加图片描述

现在,我们在 右区间 确定R

一开始,R指向2,发现prefix[R] - prefix[i] = 2 - (-2) = 4大于upper。因为右区间是 升序,所以 R和R之后 的所有元素 减去 prefix[i] 都是严格大于upper,因此R的位置被确定


1.3 求解ans

请添加图片描述

现在,我们计算和 i 满足的 j 的个数。j 只要 被L,R夹在中间,都可以满足要求。

ans = R - L = 0


part2

i = 1

现在,我们枚举左区间第二个元素


2.1 求解L

请添加图片描述

依然是先求解L

我们发现,此时 prefix[L] - prefix[i] = 2 - 0 = 2 > lower,因为 右区间 是升序,因此 L之后的所有元素 减去 prefix[i] 均大于 lower,因此L的位置确定


2.2 求解R

在这里插入图片描述

现在,我们求解R

我们发现,prefix[R] - prefix[L] = 2 = upper。因为是等于upper,所以不能确定R的位置。如果R后面的元素也是2,那直接GG,因此,我们必须保证prefix[R] - prefix[i] 严格大于upper,直到R指向最后一个元素的下一位,也就是越界位置


2.3 继续求解R

在这里插入图片描述

我们继续求解R的位置

此时,我们发现 prefix[R] - prefix[L] = 3 - 0 > upper,因为 右区间是升序,因此 R和R右边的元素 减去 prefix[i] 都是大于 upper的。因此 R的位置被确定


2.4 求解ans

请添加图片描述

现在,我们计算和i 满足的 j 的个数。j 只要 被L,R夹在中间,都可以满足要求。

ans = R - L = 1

以上,就是全部计算过程


现在,我们回看整个过程:在merge前,我们枚举 左区间 元素,充当prefix[i], 在 右区间 寻找 j 的 范围L,R

这里,我们提出几个问题

  • 为什么只枚举左区间的元素,用来充当prefix[i],而不枚举右区间元素?
  • 为什么j 的范围只在 右区间寻找?

现在,我们给出解答

  • 对于问题1:我们要注意,上述例子所展现的过程是归并的其中一个部分。不是说右区间不枚举元素,而是右区间已经枚举过了!对于右区间,又可以被拆分为 右区间的左区间右区间的右区间。在此之前,已经经过 枚举过了。如果我们在 上述例子中 时,枚举右区间, 那就重复了。
  • 对于问题2:本质上和问题1是一致的。之所以只在右区间寻找j的范围,是因为j在左区间的情况,在此之前已经寻找过了。

解法

有了上述解释,代码也就不难书写。核心就是归并排序。在merge之前,我们需要 在左区间枚举i,右区间寻找j的L,R。计算有多少对 i,j符合要求

这里我们给出归并排序的代码

//因为归并练习的时候在学习Java,所以就用Java写了,没有C++版本,淦!
class Solution {
    public int[] sortArray(int[] nums) {
        int[] tmp = new int[nums.length];
        mergeSort(nums, tmp, 0, nums.length - 1);
        return tmp;
    }
    public void mergeSort(int[] nums, int[] tmp, int lef, int rig) {
        if(lef < rig) {
            int mid = (lef + rig) >> 1;
            mergeSort(nums, tmp, lef, mid);
            mergeSort(nums, tmp, mid + 1, rig);
            merge(nums, tmp, lef, rig);
        }
    }
    public void merge(int[] nums, int[] tmp, int lef, int rig) {
        if(lef >= rig) return;
        int mid = (lef + rig) >> 1, l = lef, r = mid + 1, k = lef;
        while(l != mid + 1 && r != rig + 1) {
            if(nums[l] < nums[r]) tmp[k++] = nums[l++];
            else tmp[k++] = nums[r++];
        }
        while(l != mid + 1) tmp[k++] = nums[l++]; 
        while(r != rig + 1) tmp[k++] = nums[r++];
        for(int i = lef; i <= rig; ++i) nums[i] = tmp[i];    
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值