leetcode 315. 计算右侧小于当前元素的个数(hard)【小林优质解法】

链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

代码:

class Solution {
    int[]counts;    //用来存储结果
    int[]index; //用来绑定数据和原下标
    int[]helpNums;  //用于辅助排序 nums 数组
    int[]helpIndex; //用于辅助排序 index 数组

    public List<Integer> countSmaller(int[] nums) {
        List<Integer> list=new ArrayList<>();
        int length=nums.length;
        counts=new int[length];
        index=new int[length];
        helpNums=new int[length];
        helpIndex=new int[length];

        //初始化 index 数组
        for(int i=0;i<length;i++){
            index[i]=i;
        }

        mergeSort(nums,0,length-1);

        for(int count:counts){
            list.add(count);
        }

        return list;
    }

    //统计 nums 数组中 left ~ right 区间中的逆序对,将统计的数据保存到 counts 中
    public void mergeSort(int[] nums,int left,int right){
        //递归出口
        if(left>=right){
            return;
        }

        //int mid=(right-left)/2+left;    //获取中点
        int mid=(left+right)/2;
        mergeSort(nums,left,mid);   //统计左区间的所有逆序对
        mergeSort(nums,mid+1,right);   //统计右区间的所有逆序对

        //统计左边一个数据,右边一个数据构成的逆序对
        int cur1=left,cur2=mid+1,i=0;
        while(cur1<=mid&&cur2<=right){
            //统计逆序对
            if(nums[cur1]>nums[cur2]){
                counts[index[cur1]]+=right-cur2+1;
                //将 nums 数组和 index 数组同步修改
                helpNums[i]=nums[cur1];
                helpIndex[i++]=index[cur1++];
            }else{
                helpNums[i]=nums[cur2];
                helpIndex[i++]=index[cur2++];
            }
        }

        //处理没有遍历到的数据
        while(cur1<=mid){
            helpNums[i]=nums[cur1];
            helpIndex[i++]=index[cur1++];
        }

        while(cur2<=right){
            helpNums[i]=nums[cur2];
            helpIndex[i++]=index[cur2++];
        }

        //用辅助数组中的数据去代替原数组中的数据
        for(int j=left;j<=right;j++){
            nums[j]=helpNums[j-left];
            index[j]=helpIndex[j-left];
        }
    }
}

题解:

        本题和博主写过的另一题的解法几乎一样,推荐看leetcode LCR 170. 交易逆序对的总数(hard)【小林优质解法】

        首先我们来分析一下题意,题目要求我们统计每个数据右边有多少数据小于当前数据,还是比较好理解的,学过线性代数的同学都知道,对于 5,2 这种左边大于右边的数,我们称之为逆序对,所以题目就是要求我们统计数组中每个数据可以组成的逆序对个数

        这道题很容易想出暴力解法,就是遍历出数组中所有两个数的组合,判断其中有多少逆序对即可,但时间复杂度为 O(n^2),是比较糟糕的,在本题中会超时

        现在博主来介绍如何通过归并排序来解决该题,博主就默认大家是很理解归并排序的,要是不理解一定要先理解了归并排序才能看懂下面的内容,推荐看归并排序(递归)

        题目要求统计逆序对个数,我们可以将数组分为左右两个区间,先统计左区间的逆序对个数 A,再统计右区间的逆序对个数 B,最后统计左区间取一个数据,右区间取一个数据组成的逆序对的个数 C,通过 A+B+C 便能得到数组中所有的逆序对个数,其中,统计左区间的逆序对个数 A 和右区间的逆序对个数 B 与统计整个数组的逆序对相同,所以是递归操作,于是我们的目光就要集中在统计左区间取一个数据,右区间取一个数据组成的逆序对的个数 C 上

        我们统计 C 的个数时是在统计完 A 和 B 之后,根据归并排序的操作,此时已经排序好了左边和右边区间的数据,归并排序按照递减排序,如下图:

        用 cur1 和 cur2 遍历左边和右边的区间,找到其中的逆序对

        (1).nums[ cur1 ] > nums[ cur2 ] ,由于在左右区间数据都是递减的,所以 cur2 后面的数据都小于 cur1 指向的数据,此时我们就得到了以 cur1 指向的数据为首的一批逆序对,个数为 right - cur2 + 1 ,要将 nums[ cur1 ] 这个数据的逆序对个数添加到 counts 数组中

        现在就有一个问题,counts 数组对应的是 nums[ cur1 ] 这个数据的原下标,因为数据经过了归并排序,顺序已经打乱了,cur1 不是该数据的原下标,我们如何知道 nums[ cur1 ] 这个数据的原下标呢?这里就需要一个小技巧,我们可以在一开始的时候就创建一个数组 index ,index 数组记录了 nums 数组中每个数据的原下标,如下所示:

nums:5        2        6        1

index:0        1        2        3

        在这之后,nums 数组如何改变。index 数组就如何改变,这样处理后无论数组中的数据如何改变,我们都能知道每个数据对应的原下标是多少,所以得到 nums[ cur1 ] 这个数据的一批逆序对数目后,要执行 counts[ index[ cur1 ] ] + = right - cur2 + 1 ,在本次递归中 nums[ cur1 ] 这个数据的逆序对就获取完了,让 cur1++ 

        刚好对应归并排序,由于要按照递减排序,所以 nums[ cur1 ] > nums[ cur2 ] ,就将 cur1 指向的数据放到辅助数组 helpNums 中,此时 index 数组中的数据也要对应发生改变,放到辅助数组 helpIndex 中,再让 cur1 ++,

        (2).nums[ cur1 ] <= nums[ cur2 ] ,根据递减排序的单调性,因为 cur1 指针右边的数据都小于 cur2 指针指向的数据,所以没有数据能和 nums[ cur2 ] ,构成逆序对,让 cur2 ++,

        刚好对应归并排序,由于要按照递减排序,所以 nums[ cur1 ] 《= nums[ cur2 ] ,就将 cur2 指向的数据放到辅助数组 helpNums 中,此时 index 数组中的数据也要对应发生改变,放到辅助数组 helpIndex 中,再让 cur2 ++,

  • 21
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小林想被监督学习

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值