【路飞】327. 区间和的个数、315. 计算右侧小于当前元素的个数

1,122 阅读1分钟

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 、-12

示例 2:

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

解题思路:观察可得区间和等于前缀和相减,所有将前缀和排序,求符合区间的范围,如下图,因为前缀和已经排序了,所以当j向后移的时候 a1 大于啊,b1大于b,所以将a b 定义在for循环前面,减少寻找ab的次数,代码如下 image.png

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 个更小的元素 (21)
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;
}