剑指Offer51 数组中的逆序对

题目:

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。

示例 1:

输入: [7,5,6,4]
输出: 5

限制:

0 <= 数组长度 <= 50000


思路:利用递归,归并排序思想。

1).先将原数组进行拆分,拆分到底变成每个数组只有1个元素。

2).再进行merge操作,merge操作是将已经有序的两个数组arr[l...mid]和arr[mid + 1...r]合并为一个大的有序数组arr[l...r],在合并的过程中,统计逆序对的个数,具体思路如下:

①定义一个ret变量,用来记录归并排序时,合并当前有序小数组后逆序对的个数。

②创建一个temp临时数组,其大小和合并当前两个有序小数组之后的数组大小相同。将原数组的元素拷贝到temp中。注意:temp数组索引和原数组索引有l个单位的偏移量。

③i是第一个有序小数组(左半区间)的起始索引,j是第二个有序小数组(右半区间)的起始索引,k表示当前处理到原数组的哪个位置。

若i>mid,说明左半区间已经全部处理完毕,将右半区间的所有值写回原数组;

若j>r,说明右半区间已经全部处理完毕,将左半区间的所有值写回原数组;

若temp[i - l] <= temp[j - l],说明左半区间元素<=右半区间元素,此时不构成逆序;

若temp[i - l] > temp[j - l],说明右半区间的j元素<左半区间的i之后的所有元素,此时就构成了逆序对,将左半区间i之后的所有元素个数记录到ret中,此时i和j对应的区间中逆序对的个数 = (mid - i + 1)个。

重复上述过程,直到整个数组有序。ret就是整个数组的逆序对个数。

注:就是在merge过程中,当右半区间元素<左半区间元素,才可能构成逆序对,其他情况就是普通的归并排序。(分治思想)


代码:

class Solution {
    public int reversePairs(int[] nums) {
       return reversePairsInternal(nums, 0, nums.length - 1);
    }

    /**
     * 在nums[l...r]区间中,求逆序对的个数
     * @param nums
     * @param l
     * @param r
     * @return
     */
    private int reversePairsInternal(int[] nums, int l, int r){
        //终止条件
        if(l >= r){
            //此时数组只有一个元素,不存在逆序对
            return 0;
        }

        //将数组不断拆分
        int mid = (l + r) >> 1;
        //递归分别求出左半区间和右半区间的逆序对个数
        //左半区间和右半区间都是有序的
        int leftNum = reversePairsInternal(nums, l, mid);
        int rightNum = reversePairsInternal(nums, mid + 1, r);

        if(nums[mid] > nums[mid + 1]){
            //此时左半区间还有元素 > 右半区间,需要merge
            //整个数组的逆序对个数 = 左区间个数 + 右区间个数 + 本次merge的逆序对个数
            return leftNum + rightNum + merge(nums, l, mid, r);
        }else{
            //此时nums[mid] <= nums[mid + 1]
            //说明此时整个数组已经有序,无需再merge
            //整个数组的逆序对个数就是左区间个数 + 右区间个数
            return leftNum + rightNum;
        }
    }

    /**
     * 归并排序的merge过程,返回当前合并后逆序对的个数
     * @param nums
     * @param l
     * @param mid
     * @param r
     * @return
     */
    private int merge(int[] nums, int l, int mid, int r){
        int ret = 0;
        int[] temp = new int[r - l + 1];
        //将原数组的元素拷贝到temp
        for (int i = l; i <= r; i++) {
            temp[i - l] = nums[i];
        }

        //i是第一个有序小数组(左半区间)的起始索引
        int i = l;
        //j是第二个有序小数组(右半区间)的起始索引
        int j = mid + 1;

        //开始合并
        //k表示当前处理到原数组的哪个位置
        for (int k = l; k <= r; k++) {
            if(i > mid){
                //说明左半区间已经全部处理完毕,将右半区间的所有值写回原数组
                nums[k] = temp[j - l];
                j++;
            }else if(j > r){
                //说明右半区间已经全部处理完毕,将左半区间的所有值写回原数组
                nums[k] = temp[i - l];
                i++;
            }else if(temp[i - l] <= temp[j - l]){
                //左半区间元素 <= 右半区间元素,此时不构成逆序
                nums[k] = temp[i - l];
                i++;
            }else{
                //左半区间元素 > 右半区间元素,此时构成逆序
                //此时i和j对应的区间中逆序对的个数 = (mid - i + 1)个。
                ret += (mid - i + 1);
                nums[k] = temp[j - l];
                j++;
            }
        }
        return ret;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值