【面试题】数组中的逆序对(归并排序)

题目描述

在这里插入图片描述

Java

class Solution {
    public int reversePairs(int[] nums) {
        int len = nums.length;
        if(len < 2) return 0; // 当数组中不足2个元素时,数对都不存在,直接返回 0

        // 由于是一边归并排序一边计算逆序对个数的,我们的算法是会修改原始数组的,如果不允许修改,则需要将原始数组做一个拷贝
        int[] copy = new int[len];
        for(int i=0; i<len; i++) {
            copy[i] = nums[i];
        }
        /*
        定义一个全局的辅助数组用于归并两个有序数组,原因如下:
        1.相比于每一次调用合并函数都创建一个新的辅助数组来说,更节省内存开销和资源消耗
        2.如果是每次都新建一个数组进行归并,由于我们只是对数组的子区间进行拷贝,需要处理数组下标的偏移
        */
        int[] temp = new int [len]; 

        return recur(copy, 0, len-1, temp); // 递归实现归并排序
    }

    /*
    对nums[left..right] 计算逆序对个数并且排序,注意区间范围是左闭右闭的
    */
    private int recur(int[] nums, int left, int right, int[] temp) {
        if(left == right) return 0; // 递归终止条件:区间只剩下一个元素时,一定不存在逆序对
        // 计算当前子区间的中间位置
        int mid = left + (right - left)/2; // 若写成(left+right)/2在left和right都非常大时可能会发生整型溢出
        // 分别对当前子区间的前半部分和后半部分进行内部的逆序对计算
        int lefPairs = recur(nums, left, mid, temp);
        int rightPairs = recur(nums, mid+1, right, temp);
        // 计算跨越前后两部分之间的逆序对
        if(nums[mid] <= nums[mid+1]) return lefPairs + rightPairs; // 如果数组已经有序了,不需要归并了
        int crossPairs = mergeAndCount(nums, left, mid, right, temp);
        // 累加这三部分即为整个区间的逆序对个数
        return lefPairs + rightPairs + crossPairs;
    }

    /*
    合并前提:nums[left...mid] 有序,且nums[mid+1...right] 有序
    */
    private int mergeAndCount(int[] nums, int left, int mid, int right, int[] temp) {
        // 将 left到 right之间的元素拷贝到 temp数组中
        for(int i=left; i<=right; i++) { // 注意区间范围是左闭右闭,因此是<=right
            temp[i] = nums[i];
        }
        // 定义两个指针,初始化分别指向前后两部分的首个元素
        int i = left, j = mid+1;
        int count = 0;
        for(int k=left; k<=right; k++) {
            if(i == mid+1){ // 当前半部分已经没有剩下元素时
                nums[k] = temp[j];
                j++;
            }
            else if(j == right+1){ // 当后半部分已经没有剩下元素时
                nums[k] = temp[i];
                i++;
            }
            else if(temp[i] <= temp[j]) { // ps:如果写成 < 则归并排序不是稳定的排序了
                nums[k] = temp[i];
                i++;
            }
            else {
                nums[k] = temp[j];
                j++;
                // 只有在归并后半部分的元素时,才需要计算逆序对个数,等于第一个数组还没有被归并回去的元素个数
                count += (mid-i+1); 
            }
        }
        return count;
    }
}

参考

https://leetcode-cn.com/problems/shu-zu-zhong-de-ni-xu-dui-lcof/solution/shu-zu-zhong-de-ni-xu-dui-by-leetcode-solution/

https://leetcode-cn.com/problems/shu-zu-zhong-de-ni-xu-dui-lcof/solution/jian-dan-yi-dong-gui-bing-pai-xu-python-by-azl3979/

https://leetcode-cn.com/problems/shu-zu-zhong-de-ni-xu-dui-lcof/solution/bao-li-jie-fa-fen-zhi-si-xiang-shu-zhuang-shu-zu-b/

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值