[路飞]_程序员必刷力扣题: 327. 区间和的个数

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

327. 区间和的个数

给你一个整数数组 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 位 的整数

归并排序

思路

要求区间和,就是吧从i到j之间的值相加(i<=j)

怎么用归并呢,排序数组不久乱了?

这里我们可以使用归并排序,但是区间和怎么能和区间和产生关系呢?区间和是原数组中某一段元素的和,如果排序那么数据就会发生混乱,得不到原来的区间和。

这里我们可以巧妙的运用前缀和,将从i到j的区间的和保存在新的前缀和数组的j位(准确的说是j+1位),这样我们就可以通过区间和的第j位减去第i位得到原数组中i+1到j之间的区间和。

因为区间和的元素可以为1位,那么要求首位的值的话我们需要在区间和0位放置一个数字0,那么原数组中的[0,1] [0,1] [0,..j]就可以通过前缀和的第j位减去第0位求得,所以准确来说是保存在第j+1位中

怎么能快速得到满足条件的区间和数量呢?

前缀和的所有元素就可以不用在乎位置而随意排序,变成有序数组那么再求[lower,upper]就会快很多,因为无需全部遍历,在归并过程中我们会得到两个有序数组sums1和sums2,我们用i标记sums1的首位,l和r标记sums2的首位,遍历sums1中的每一位和sums2中的每一位作差(sums2[l]-sums1[i])

  • 如果结果小于lowwer就不符合题意,继续l++,直到差结果大于等于lowwer,那么从l开始就是一个满足于题意的区间和
  • r>=l,只要r在有效下标以内,那么r继续往后移动,sums[r]-sums[i]如果小于upper那么继续移动r++,直到sums的最后一位
  • 此时[l,r)减去i都是满足题意的结果,一共r-l次

具体实现:

  • 求出前缀和进行归并排序
  • 将前缀和从中间一分为二,递归处理left和right部分,递归终止条件为分割成1位元素时自然是有序的,直接返回本身以及满足条件的总次数0
  • 依照上面思路求两个有序序列之间满足条件的数量
  • 最后进行排序,排序结束则得到最终答案
var countRangeSum = function (nums, lower, upper) {
    //求区间和联想到前缀和前缀和
    //区间和 S(i, j) 表示在 nums 中,位置从 i 到 j 的元素之和,包含 i 和 j (i ≤ j)。
    //那么在前缀和sums中,可以用sums[j]-sums[i-1](因为包含了i和j)来表示区间和S(i,j)。所以为了表达端点需要在前缀和初始值增加一个0.
    var sums = [0]
    var total = 0
    for (var i = 0; i < nums.length; i++) {
        sums.push(total += nums[i])
    }
    const result = computedS(sums, lower, upper)
    return result[1]
};
var computedS = function (sums, lower, upper) {
    if (sums.length < 2) return [sums, 0]
    var mid = Math.floor((sums.length) / 2);
    var left = sums.slice(0, mid)
    var right = sums.slice(mid)
    var [sums1, n1] = computedS(left, lower, upper)
    var [sums2, n2] = computedS(right, lower, upper)
    var res = n1 + n2
    // 声明l和r指向右侧数组的第一项
    var i = 0;
    var l = r = 0
    // 循环统计
    while (i <= mid - 1) {
        while (l <= sums2.length - 1 && sums2[l] - sums1[i] < lower) l++
        while (r <= sums2.length - 1 && sums2[r] - sums1[i] <= upper) r++
        res += (r - l)
        i++
    }


    var arr = []
    var p1 = p2 = 0
    var index = 0
    while (p1<sums1.length || p2<sums2.length) {
        if (p1 >= sums1.length) {
            arr[index++] = sums2[p2++]
        } else if (p2 >= sums2.length) {
            arr[index++] = sums1[p1++]
        }else if (sums1[p1] < sums2[p2]) {
            arr[index++] = sums1[p1++]
        } else {
            arr[index++] = sums2[p2++]
        }
    }
    return [arr, res]
}
复制代码

猜你喜欢

转载自juejin.im/post/7055116390771982366