LeetCode - 归并排序 + 索引数组 - 315. 计算右侧小于当前元素的个数

题目 315. 计算右侧小于当前元素的个数

难度 困难

给定一个整数数组 nums,按要求返回一个新数组 counts。数组 counts 有该性质: counts[i] 的值是 nums[i] 右侧小于 nums[i] 的元素的数量。

示例:

输入: [5,2,6,1]
输出: [2,1,1,0]
解释:
5 的右侧有 2 个更小的元素 (2 和 1).
2 的右侧仅有 1 个更小的元素 (1).
6 的右侧有 1 个更小的元素 (1).
1 的右侧有 0 个更小的元素.

解题思路

普通遍历算法肯定超时,应该思考如何简化算法。
这里采用 归并排序 + 索引数组的算法

整个的思路是这样子的:
在归并排序的过程中,统计每个前有序数组中每个数的逆序数个数。

我们首先要知道什么是归并排序,这个在排序算法中有讲,我就不说了。
然后要在归并排序的过程中,统计每个数的逆序数的个数。
那么如何统计逆序数的个数呢?我们在归并的时候,总是将数组分割为两部分,然后将两部分进行排序,分为前有序数组和后有序数组。
在这里插入图片描述

求解 “逆序对” 的关键在于:当其中一个数字放进最终归并以后的有序数组中的时候,这个数字与之前看过的数字个数(或者是未看过的数字个数)可以直接统计出来,而不必一个一个数”。

而本题目的要求是:让我们求 “在一个数组的某个元素的右边,比自己小的元素的个数”
因此,我们就 应该在 “前有序数组” 的元素出列的时候,数一数 “后有序数组” 已经出列了多少元素,因为这些已经出列的元素都比当前出列的元素要小(或者等于)。下面这幅图可能比较好理解。
在这里插入图片描述

但是与此同时,我们也发现一个问题,归并排序后,数组的下标会发生变化,那要怎样去定位原始数组的下标呢?这里采用一个索引数组的办法。索引数组的原理是这样的:
在这里插入图片描述
也就是说,我们不管归并之后怎么变化,用一个临时数组来记录原数组的下标。这样在统计每个数的数量时候,就能够精确定位

总结:

1、我们借助计算 “逆序数” 的思路完成本题,关键在于这里我们只能在 “前有序数组” 出列的时候计算逆序数;这点很关键,一定要弄清楚逆序数的求法。
如果题目让我们计算 “nums[i] 左侧小于 nums[i] 的元素的数量” 可以在 “后有序数组” 出列的时候计算逆序数;
2、体会 “索引数组” 这个使用技巧。

代码

import java.util.ArrayList;
import java.util.List;

public class Solution {

    private int[] temp;
    private int[] counter;
    private int[] indexes;

    public List<Integer> countSmaller(int[] nums) {
        List<Integer> res = new ArrayList<>();
        int len = nums.length;
        if (len == 0) {
            return res;
        }
        temp = new int[len];
        counter = new int[len];
        indexes = new int[len];
        for (int i = 0; i < len; i++) {
            indexes[i] = i;
        }
        mergeAndCountSmaller(nums, 0, len - 1);
        for (int i = 0; i < len; i++) {
            res.add(counter[i]);
        }
        return res;
    }

    /**
     * 针对数组 nums 指定的区间 [l, r] 进行归并排序,在排序的过程中完成统计任务
     *
     * @param nums
     * @param l
     * @param r
     */
    private void mergeAndCountSmaller(int[] nums, int l, int r) {
        if (l == r) {
            // 数组只有一个元素的时候,没有比较,不统计
            return;
        }
        int mid = l + (r - l) / 2;
        mergeAndCountSmaller(nums, l, mid);
        mergeAndCountSmaller(nums, mid + 1, r);
        // 归并排序的优化,同样适用于该问题
        // 如果索引数组有序,就没有必要再继续计算了
        if (nums[indexes[mid]] > nums[indexes[mid + 1]]) {
            mergeOfTwoSortedArrAndCountSmaller(nums, l, mid, r);
        }
    }

    /**
     * [l, mid] 是排好序的
     * [mid + 1, r] 是排好序的
     *
     * @param nums
     * @param l
     * @param mid
     * @param r
     */
    private void mergeOfTwoSortedArrAndCountSmaller(int[] nums, int l, int mid, int r) {
        // 3,4  1,2
        for (int i = l; i <= r; i++) {
            temp[i] = indexes[i];
        }
        int i = l;
        int j = mid + 1;
        // 左边出列的时候,计数
        for (int k = l; k <= r; k++) {
            if (i > mid) {
                indexes[k] = temp[j];
                j++;
            } else if (j > r) {
                indexes[k] = temp[i];
                i++;
                // 此时 j 用完了,[7,8,9 | 1,2,3]
                // 之前的数就和后面的区间长度构成逆序
                counter[indexes[k]] += (r - mid);
            } else if (nums[temp[i]] <= nums[temp[j]]) {
                indexes[k] = temp[i];
                i++;
                // 此时 [4,5, 6   | 1,2,3 10 12 13]
                //           mid          j
                counter[indexes[k]] += (j - mid - 1);
            } else {
                // nums[indexes[i]] > nums[indexes[j]] 构成逆序
                indexes[k] = temp[j];
                j++;
            }
        }
    }

    public static void main(String[] args) {
        int[] nums = new int[]{5, 2, 6, 1};
        Solution solution = new Solution();
        List<Integer> countSmaller = solution.countSmaller(nums);
        System.out.println(countSmaller);
    }
}

作者:liweiwei1419
链接:https://leetcode-cn.com/problems/count-of-smaller-numbers-after-self/solution/gui-bing-pai-xu-suo-yin-shu-zu-python-dai-ma-java-/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值