区间和个数

237 阅读1分钟

题目描述

leetcode-cn.com/problems/co…

分析

题目要求看的是某个区间范围内,所有数组元素的和是否在 lowerupper 之间,注意题目上这里面的不等关系是否有等号

解题思路

对于区间问题,很容易地想到前缀和,因为通过这么一个前缀和数组可以很方便地算出来两个下标之间的元素,它的和是多少

算法,数据结构

前缀和,归并排序

思路

首先求得前缀和数组,然后看下着么转化这个问题,让他更好解决

对于前缀和而言,求 [i, j] 的元素实际上在前缀和里就是求两个索引值的差

把它称为 nums[j] - nums[i]

根据题目要求,实际上是要求他 lower <= nums[j] - nums[i] <= upper

如果遍历到一个 j 之后,我想找 i,实际上可以把不等式通过移项:

nums[j] - upper <= num[i] <= nums[j] - lower

把两边写简单点的话:

a <= nums[i] <= b

所以实际上就是要找两个索引,k1, k2,满足上边的等式,如果我找的范围是有序

k1 指向第一个符合 a 的 index 让 k2 指向第一个符合 b 的 index

对于当前 nums[j],符合符合条件的区间和个数是 k2 - k1

所以怎么有序呢?归并排序啊!

过程

求前缀和

设变量 sum 为前缀和,初始值为 0 index 是 0,一般前缀和都这么弄

遍历 nums 数组参数,设置 sum,每一项都是 sum 的前一项加上当前遍历到的 nums 元素

归并排序

开始归并排序的过程,这里是对前缀和进行归并,我们统计的过程在合并之前进行,因为是对左右两个有序的区间进行

统计区间个数

传参

这个统计过程放在另一个函数进行,要接收两个区间的边界(l1, r1; l2, r2)

变量

设置 k1, k2 两个变量,初始值都是 l1,另一个变量 ans 作为统计

遍历找一个 j

遍历区间,范围在 [l2, r2],计算 a, b

找 k1, k2

接下来,要找符合要求的 k1, k2,这里是关键!

由于单调递增,可以显然看出 k1, k2 只能是从左向右找,因此也是递增的

通过 while 循环,来找到一个 k1,使得它刚好符合 a 通过 while 循环,来找到一个 k2,使得它刚好不符合 b

此时答案是 k2 - k1

以上就是主要的统计过程,在统计之后,需要把两个区间合并~

代码

/**
 * @param {number[]} nums
 * @param {number} lower
 * @param {number} upper
 * @return {number}
 */
var countRangeSum = function (nums, lower, upper) {
  const sum = [0]
  for (let i = 0; i < nums.length; i++) {
    sum[i + 1] = nums[i] + sum[i]
  }

  return mergeSort(sum, 0, sum.length - 1, lower, upper)
}

function mergeSort(sum, l, r, lower, upper) {
  if (l >= r) return 0
  const mid = (l + r) >> 1,
    temp = []
  let p1 = l,
    p2 = mid + 1,
    ans = 0,
    k = 0

  ans += mergeSort(sum, p1, mid, lower, upper)
  ans += mergeSort(sum, p2, r, lower, upper)
  ans += countTwoParts(sum, p1, mid, p2, r, lower, upper)

  while (p1 <= mid || p2 <= r) {
    if (p2 > r || (p1 <= mid && sum[p1] <= sum[p2])) {
      temp[k++] = sum[p1++]
    } else {
      temp[k++] = sum[p2++]
    }
  }
  for (let i = l; i <= r; i++) {
    sum[i] = temp[i - l]
  }

  return ans
}

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

  // 找一个 sum[j]
  for (let j = l2; j <= r2; j++) {
    let a = sum[j] - upper
    let b = sum[j] - lower

    // 求第一个位置 >= a,第一个位置 > b
    while (k1 <= r1 && sum[k1] < a) k1++
    // 此时 k1 指向的是第一个 >= a 的位置

    while (k2 <= r1 && sum[k2] <= b) k2++
    // 此时 k2 指向的是第一个 > b 的位置

    ans += k2 - k1
  }

  return ans
}