剑指offer(37):数组中的逆序对

题目描述

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

分析

直接思路:顺序扫描整个数组,每次扫描到一个数字,将该数字逐个与后面的数字比较,如果满足逆序关系,则统计量count加1。如果有n个数字,每个数字都需要和 O(n) 个数字进行比较,则总的而时间复杂度为 O(n)

推荐解法:参考归并排序的思路,将大数组不断进行分割,分割成为左右两个子数组,分别对左右子数组进行递增排序并统计左右子数组中的逆序对的数目;再将左右子数组进行合并,统计合并之后的逆序对的的数目。归并排序是一个递归调用的过程,以 O(n) 的空间换取时间,时间复杂度为 O(nlogn) ,则总的逆序对数目为左子数组中、右子数组中、合并之后的逆序对数目的和。

牛客AC:

package com.problem;

public class InversePairs {

    public static void main(String[] args) {
        InversePairs inversePairs = new InversePairs();
        int[] arr = {7, 5, 6, 4};
        System.out.println(inversePairs.inversePairs(arr));
    }

    public int inversePairs(int[] arr) {
        if(arr == null || arr.length <= 0)
            return 0;

        int length = arr.length;

        // 复制一份数组,辅助空间
        int[] copyArr = new int[length];
        for(int i = 0; i < length; i++) 
            copyArr[i] = arr[i];

        int count = getInversePairs(arr, copyArr, 0, length - 1);
        return count;
    }

    /**
     * 
     * @param iniArr    初始数组
     * @param copyArr   辅助数组
     * @param start 起始索引
     * @param end   结束索引
     * @return
     */
    public int getInversePairs(int[] iniArr, int[] copyArr, int start, int end) {
        if(start == end) {
            copyArr[start] = iniArr[start];
            return 0;
        }

        // 对左右子数组归并排序,排序过程中统计逆序数
        int midLength = (end + start) / 2;
        int leftCount = getInversePairs(iniArr, copyArr, start, midLength);
        int rightCount = getInversePairs(iniArr, copyArr, midLength + 1, end);

        // 归并
        int mergeCount = mergeInversePairs(iniArr, copyArr, start, midLength + 1, end);

        // 总的逆序数等于左右子数组中的逆序数加上合并之后的逆序数
        return leftCount + rightCount + mergeCount;
    }
    /**
     *  在归并排序过程中统计逆序数
     * @param iniArr    初始数组
     * @param copyArr   复制数组
     * @param leftStart 左子数组起始索引
     * @param rightStart    右子数组起始索引
     * @param rightEnd  右子数组结束索引
     * @return  合并之后的逆序数
     */
    public int mergeInversePairs(int[] iniArr, int[] copyArr, int leftStart, int rightStart, int rightEnd) {
        int leftEnd = rightStart - 1;
        int indexCopy = rightEnd;
        int nElements = rightEnd - leftStart + 1;

        // i,j初始指向子数组的尾部
        int i = leftEnd;
        int j = rightEnd;

        int count = 0;  // 逆序数个数
        while(i >= leftStart && j >= rightStart) {
            // 由于左边子数组都是递增排序的,如果左边元素大于右边元素,逆序数为整个右子数组的个数
            if(iniArr[i] > iniArr[j]) {
                copyArr[indexCopy--] = iniArr[i--];
                count += j - rightStart + 1;    
            } else {
                copyArr[indexCopy--] = iniArr[j--];
            }
        }

        // 复制左子数组剩余元素
        while(i >= leftStart)
            copyArr[indexCopy--] = iniArr[i--];

        // 复制右子数组剩余元素
        while(j >= rightStart)
            copyArr[indexCopy--] = iniArr[j--];

        // 将结果数组替换原数组,参与后续递归的归并排序
        // 最后的结果iniArr和resArr数组都已经经过了排序,完全一致
        for (int k = 0; k < nElements; k++) {
            iniArr[rightEnd] = copyArr[rightEnd];
            rightEnd--;
        }

        return count;
    }
}

参考
1. 何海涛,剑指offer名企面试官精讲典型编程题(纪念版),电子工业出版社

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值