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
解题思路:观察可得区间和等于前缀和相减,所有将前缀和排序,求符合区间的范围,如下图,因为前缀和已经排序了,所以当j向后移的时候 a1 大于啊,b1大于b,所以将a b 定义在for循环前面,减少寻找ab的次数,代码如下
var countRangeSum = function(nums, lower, upper) {
//判断边界条件
if(nums.length < 1) return 0;
//统计前缀和值
let ans = 0;
//保存前缀和值
let ansList = [0];
//统计区间和数组
for(let i = 0; i < nums.length; i ++){
ans += nums[i];
ansList.push(ans);
}
return count_range_sum(ansList,0,ansList.length -1,lower,upper);
};
let count_range_sum = function(arr,l,r,low,upp){
// 递归结束条件
if(l >= r) return 0;
//获得中间分割索引值
let m = (l+r) >> 1,ans = 0;
//左边数组排序
ans += count_range_sum(arr,l,m,low,upp);
//右边数组排序
ans += count_range_sum(arr,m + 1,r,low,upp);
//统计符合条件的数量
ans += count_two_part(arr,l,m,m + 1,r,low,upp);
let k = l,p1 = l, p2 = m + 1,temp = [];
//归并查分
while(p1 <= m || p2 <= r){
if(p2 > r || (p1 <= m && arr[p1] <= arr[p2])){
temp[k ++] = arr[p1 ++];
} else {
temp[k ++] = arr[p2 ++];
}
}
//归并合并过程
for(let i = l; i <= r; i ++) arr[i] = temp[i];
return ans;
}
//统计左边符合条件的范围值
let count_two_part = function(arr,l1,r1,l2,r2,low,upp){
// k1是区间小的值的指针,k2是区间大值的指针
let k1 = l1,k2 = l1,ans = 0;
//循环右边数组,在左边找出范围内的值个数
for(let j = l2; j <= r2; j ++){
//计算区间值
let a = arr[j] - upp,b = arr[j] - low;
//找出左边值,因为是有序的数组,所有当j向后移的时候,新的k1大于之前的k1,所以定义在外面
while(k1 <= r1 && arr[k1] < a) k1 ++;
//找出右边值
while(k2 <= r1 && arr[k2] <= b) k2 ++;
//累计总个数
ans += k2 - k1;
}
return ans;
}
315. 计算右侧小于当前元素的个数
给你一个整数数组 nums ,按要求返回一个新数组 counts 。数组 counts 有该性质: counts[i] 的值是 nums[i] 右侧小于 nums[i] 的元素的数量。 示例 1:
输入:nums = [5,2,6,1]
输出:[2,1,1,0]
解释:
5 的右侧有 2 个更小的元素 (2 和 1)
2 的右侧仅有 1 个更小的元素 (1)
6 的右侧有 1 个更小的元素 (1)
1 的右侧有 0 个更小的元素
示例 2:
输入: nums = [-1]
输出: [0]
示例 3:
输入: nums = [-1,-1]
输出: [0,0]
解题思路:这题的解题思路其实也是利用归并排序,只不过是由大到小排序,然后在合并的时候计算右边小于当前左边值的数量,进行累计就可了,这题要求返回的是每个数右边小于的数量,所以需要记录当前值的索引来进行分别累计,之所以要根据索引来累计是因为nums有重复数字,代码如下
var countSmaller = function(nums) {
//保存重新组装nums数组
let arr = [];
//根据索引累计小的值
let ans = {};
//重组nums
for(let i = 0; i < nums.length; i ++){
arr.push({val:nums[i],cnt:i});
ans[i] = 0;
}
//将arr按照大到小排序,累计ans索引对应值
count_smaller(arr,0,arr.length -1,ans);
let data = [];
for(let i = 0; i < nums.length; i ++) data[i] = ans[i];
return data;
};
var count_smaller = function(arr,l,r,ans){
if(l >= r) return;
let m = (l + r) >> 1;
count_smaller(arr,l,m,ans);
count_smaller(arr,m + 1,r,ans);
let k = l, p1 = l, p2 = m + 1,temp = [];
while(p1 <= m || p2 <= r){
if(p2 > r || (p1 <= m && arr[p1].val > arr[p2].val)){
//当右边存在,根基cnt获得当前值的索引,累计小于当前值的总和
if(p2 <= r) ans[arr[p1].cnt] += (r - p2 + 1);
temp[k ++] = arr[p1 ++];
} else {
temp[k ++] = arr[p2 ++]
}
}
for(let i = l; i <= r; i ++) arr[i] = temp[i];
return;
}