[路飞]_LeetCode_327. 区间和的个数

381 阅读2分钟

「这是我参与2022首次更文挑战的第31天,活动详情查看:2022首次更文挑战

题目

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

区间和 S(i, j) 表示在 nums 中,位置从 i 到 j 的元素之和,包含 i 和 j (i ≤ j)。

示例 1:

输入:nums = [-2,5,-1], lower = -2, upper = 2
输出:3
解释:存在三个区间:[0,0][2,2][0,2] ,对应的区间和分别是:-2 、-1 、2 。

示例 2:

输入:nums = [0], lower = 0, upper = 0
输出:1

提示:

1 <= nums.length <= 105
-231 <= nums[i] <= 231 - 1
-105 <= lower <= upper <= 105
题目数据保证答案是一个 32 位 的整数

来源:力扣(LeetCode)leetcode-cn.com/problems/co…

解题思路

以 [-2,5,-1] 为例,

区间和为:[0, 0] = -2,[0, 1] = 3,[0, 2] = 2,[1, 1] = 5,[1, 2] = -4,[2, 2] = -1

前缀和为:sum = [0, -2, 3, 2]

从上面的结果可以看出,i = 0,j = 2 时,nums[0, 2] = 2,sum[2] - sum[0] = 2,即:

  • nums[i, j] = sum[j] - sum[i] = 2

因此可以得出,原序列区间和 = 前缀和数组中两项相减的值,即:

  • nums[i, j] = sum[j] - sum[i]

所以可以把原问题是区间和在一个范围内,转换成前缀和两项相减在一个范围内

  • 区间最大值范围:sum[j] - sum[i] ≤ upper => sum[i] ≥ sum[j] - upper

  • 区间最小值范围:sum[j] - sum[i] ≥ lower => sum[i] ≤ sum[j] - lower

  • 得出 sum[j] - lower ≥ sum[i] ≥ sum[j] - upper,即:max ≥ sum[i] ≥ min

那么我们首先可以得到数组的前缀和,并且对前缀和进行归并排序,归并排序时右边数组为sum[j],再去左边数组中找 sum[i] 满足条件的元素个数。

代码实现

var countRangeSum = function (nums, lower, upper) {
    const len = nums.length
    
    //初始化前缀和并设置第一个元素为0
    const sum = new Array(len + 1)
    sum[0] = 0
    //计算前缀和
    for (let i = 0; i < len; i++) sum[i + 1] = sum[i] + nums[i]
    
    //通过对前缀和归并排序计算区间和在指定范围内的个数
    return mergeSort(sum, 0, sum.length - 1, lower, upper)
};

var mergeSort = function (nums, l, r, lower, upper) {
    //如果l大于等于r时,区间只有一个元素或没有元素,不需要排序,个数为0
    if (l >= r) return 0

    //获取中间位置
    const mid = (l + r) >> 1
    let ans = 0
    
    //对 nums 的 l 到 mid 区间排序,并累加结果
    ans += mergeSort(nums, l, mid, lower, upper)
    //对 nums 的 mid + 1 到 r 区间排序,并累加结果
    ans += mergeSort(nums, mid + 1, r, lower, upper)
    
    //计算左右两边数组满足条件的元素个数
    ans += countResult(nums, l, mid, mid + 1, r, lower, upper)

    let p1 = l, p2 = mid + 1
    const arr = []
    
    while (p1 <= mid || p2 <= r) {
        //如果右边的数组已经为空,或者左边第一个元素小于右边数组的第一个元素时,把左边的元素加入结果中
        //否则把右边的第一个元素加入结果中
        if ((p2 > r) || (p1 <= mid && nums[p1] <= nums[p2])) {
            arr.push(nums[p1++])
        } else {
            arr.push(nums[p2++])
        }
    }

    //复制排序后的结果到原数组
    for (let i = l; i <= r; i++) nums[i] = arr[i - l]

    return ans
}

var countResult = function (sum, l1, r1, l2, r2, lower, upper) {
    let ans = 0, k1 = k2 = l1

    for (let j = l2; j <= r2; j++) {
        //计算区间最小值
        const min = sum[j] - upper
        //计算区间最大值
        const max = sum[j] - lower
        
        //计算最小值的开始位置
        while (k1 <= r1 && sum[k1] < min) k1++
        //计算最大值的结束位置
        while (k2 <= r1 && sum[k2] <= max) k2++
        
        //k2 - k1 得到元素个数,这里没有减1,因为最后一次 k2++ 不在范围内
        ans += k2 - k1
    }

    return ans
}

如有错误欢迎指出,欢迎一起讨论!