剑指Offer---面试题36:数组中的逆序对

一.题目

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。
例如:{7,5,6,4},一共存在五个逆序对:{7,5},{7,6},{7,4},{5,4},{6,4};


二.分析

思路1:显而易见,从头到尾扫描数组,没扫描到一个数字,就拿去和后面所有的数组进行比较,两个for循环,解决问题,算法时间复杂度为o(n^2),

思路2:跳跃性的联想到merge_sort,在merge函数中,我们有一个过程是这样的,对左(leftArray)右(rightArray)两个已经排好序的数组进行整合,使整合之后的数组任然是排好序的,经典的算法思路如下:

[Merge Sort算法 浙江大学 陈越老师的视频,好的不要不要的
—出自中国大学MOOC网]

  1. 一个索引(leftstart)指向左边数组的第一位,一个索引(rightStart)指向右边数组的第一位,一个索引(tmpPos)指向临时的第一位

  2. 将左右侧数组索引所指向的位置处的数字进行比较,将小的数组存放进临时数组,然后相应的移动指针.

  3. leftArray[leftStart]>rightArray[rightStart]时,试想,leftArray中的所有数字是不是都大于rightArray[rightStart]???(因为leftArray是排好序的),那么leftArray.length也就统计了一次逆序对.

  4. 当我们递归的过程进行完了,所有的逆序对也就统计完成了

  5. 经典归并排序的时间复杂度为0(nlogn)


四.代码

public class Solution {
    int count;

    public int InversePairs(int[] array) {
        int n=array.length;
        count = 0;
        int[]tmpA=new int[n];
        if (n != 0)          
            mergeSort(array,tmpA, 0, n - 1);
        return count;
    }

    /**
     * 归并排序--递归形式
     *
     * @param a        待排序的数组
     * @param tmpA     临时创建的数组
     * @param leftStart左边数组的起始位置
     * @param rightEnd 右边数组的终止位置
     * @author ZFY
     */
    public void mergeSort(int[] a, int[] tmpA,int leftStart, int rightEnd) {
        if (leftStart>= rightEnd)
            return;
        int center = (leftStart+ rightEnd) /2; 
        mergeSort(a,tmpA, leftStart, center);//递归排序左边
        mergeSort(a,tmpA, center + 1, rightEnd); //递归排序右边
        merge(a,tmpA, leftStart, center+1, rightEnd);//归并
    }

    /**
     * 归并过程 排序完成的序列在原序列a中
     *
     * @param a        待排序数组
     * @param tmpA     临时数组
     * @param leftStart  左边数组的起始位置
     * @param rightStart 右边数组的起始位置
     * @param rightEnd 右边数组的结束位置
     * @author ZFY
     */
    public void merge(int[] a,int[] tmpA, int leftStart, int rightStart, int rightEnd) {
        int leftEnd=rightStart-1;
        int tmpPos = leftStart;
        int eNum=rightEnd-leftStart+1;
        while (leftStart <= leftEnd && rightStart <= rightEnd) {
            if (a[leftStart] <= a[rightStart])
                tmpA[tmpPos++] = a[leftStart++];
            else {
                tmpA[tmpPos++] = a[rightStart++];
                count += leftEnd - leftStart + 1;   
                // 左右两侧的序列都是已经排好序的序列(递增),当左边的第一个数字大于右边第一个数字时
                //那个左侧序列的所有数字都大于右边第一个
               //例如: {4,5,6,7,8} {3,4,5,6} 左边的4大于右边的3,那么左侧所有的都大于3,
               //左侧所有的个数为leftEnd - leftStart + 1;

            }
        }

        while (leftStart <= leftEnd)
            tmpA[tmpPos++] = a[leftStart++];
        while (rightStart <= rightEnd)
            tmpA[tmpPos++] = a[rightStart++];
        for(int l=0;l<eNum;l++,rightEnd--){
            a[rightEnd]=tmpA[rightEnd];
        }
    }
}

将第50行代码去掉,剩下的代码就是一个完整的归并排序的递归算法.排好序的数组任然存放于原数组array中;


五.小结

由这个题目也可以看出来,归并排序算法说到底就是在消除序列中的逆序对,当序列中的逆序对为0的时候,这个序列就是已经被排好序了.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值