「这是我参与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
}
如有错误欢迎指出,欢迎一起讨论!