[路飞]_LeetCode_315. 计算右侧小于当前元素的个数

156 阅读1分钟

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

题目

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

提示:

1 <= nums.length <= 105
-104 <= nums[i] <= 104

来源:力扣(LeetCode)leetcode-cn.com/problems/co…

解题思路

在做倒序的归并排序时,如果左边第一个元素大于右边第一个元素,那么右边数组中的元素数量就是改元素的右侧小于当前元素个数。

所以我们可以在归并时统计右侧小于当前元素的个数,这里我们需要用到一个新的数据结构:

  • { val: 0, ind: 0, cnt: 0 }
  • val: 当前元素
  • ind: 当前元素在原数组中的位置,用于统计后组装结果
  • cnt: 存放统计结果

代码实现

var countSmaller = function (nums) {
    const arr = []
    const len = nums.length
    
    //将数组转换成新的数据结构
    //新的数据结构包括当前值、索引(用于统计后重新组装)、和右侧小于当前元素个数
    for (let i = 0; i < len; i++) {
        arr.push({ val: nums[i], ind: i, cnt: 0 })
    }

    //对数组进行归并排序
    mergeSort(arr, 0, arr.length - 1)

    //重新组装数组返回:将统计结果放到原数组对应的索引位置
    const ans = new Array(len)
    arr.forEach(item => {
        ans[item.ind] = item.cnt
    })

    return ans
};

var mergeSort = function (nums, l, r) {
    //如果l大于等于r时,区间只有一个元素或没有元素,不需要排序,个数为0
    if (l >= r) return 0

    //获取中间位置
    const mid = (l + r) >> 1
    //对 nums 的 l 到 mid 区间排序
    mergeSort(nums, l, mid)
    //对 nums 的 mid + 1 到 r 区间排序
    mergeSort(nums, mid + 1, r)

    const arr = []
    let p1 = l, p2 = mid + 1
    while (p1 <= mid || p2 <= r) {
        //如果右边的数组已经为空,或者左边第一个元素大于右边数组的第一个元素时,把左边的元素加入结果中
        //否则把右边的第一个元素加入结果中
        if ((p2 > r) || (p1 <= mid && nums[p1].val > nums[p2].val)) {
            //累计右边数组中小于当前元素的个数
            nums[p1].cnt += r - p2 + 1
            
            arr.push(nums[p1++])
        } else {
            arr.push(nums[p2++])
        }
    }

    //复制排序后的结果到原数组
    for (let i = l; i <= r; i++) nums[i] = arr[i - l]
}

如有错误欢迎指出,欢迎一起讨论!