剑指Offer-51:数组中的逆序对

题目描述

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


        这道题的第一思路就是顺序扫描整个数组,然后每扫到一个数字,就拿后面的数字与它比较,如果后面的数字比它小,则这两个数字就组成了一个逆序对。假设数组中含有n个数,由于每个数字都要和O(n)个数字进行比较,因此这种算法的时间复杂度是O(n^2)。

        为了更好的时间效率,可以考虑优先比较两个相邻的数字。以数组{7,5,6,4}为例在分析统计逆序对的过程。

        如下图所示,先把数组分解成两个长度为2的子数组,再把这两个子数组分别拆分成两个长度为1的子数组。接下来一边合并相邻的子数组,以便统计逆序对的数目。在第一对长度为1的子数组{7}、{5}中,7大于5,因此(7,5)也是一个逆序对。同样的,在第二对长度为1的子数组{6}、{4}中,也有逆序对(6,4)。由于已经统计了这两对子数组内部的逆序对了,因此需要把这两对子数组排序,以免在以后的统计过程中再避免重复。
这里写图片描述
        统计逆序对的过程如下:夏娜吧数组分割成子数组,统计出子数组内部的逆序对的数目,然后再统计出两个相邻子数组之间的逆序对的数目。在统计逆序对的过程中,还需要对数组进行排序。如果对排序算法很熟悉,不难发现,以上过程就是一个归并排序

 利用归并排序的思想,先将数组分解成为n个长度为1的子数组,然后进行两两合并同时排好顺序。

在对两个子区域合并排序时,记左边区域(下标为startmid)的指针为i,右边区域(下标为mid+1end)的指针为j,两个指针都指向该区域内最大的数字,排序时:

(1)如果i指向的数字大于j指向的数字,说明:逆序对有j-mid个,我们把i指向的数字放入临时创建的排序数组中,然后令i-1,指向该区域前一个数字,继续进行排序;

(2)如果i指向的数字小于等于j指向的数字,说明暂时不存在逆序对,将j指向的数字放入临时创建的排序数组中,然后令j-1,指向该区域前一个数字,继续进行排序;

(3)某一子区域数字都放入排序数组后,将另一个子区域剩下的数字放入排序数组中,完成排序;

(4)最后将排序好的数字按顺序赋值给原始数组的两个子区域,以便合并后的区域与别的区域合并。

在这里插入图片描述

public class InversePairs {
    public static int inversePairs(int [] array) {
        if(array==null || array.length<=0)
            return 0;
        int count=getCount(array,0,array.length-1);
        return count;
    }
     
    private static int getCount(int[] array,int start,int end){
        if(start>=end)
            return 0;
        int mid=(end+start)>>1;
        int left=getCount(array,start,mid);
        int right=getCount(array,mid+1,end);
         
        //合并
        int count=0;
        int i=mid; //左边区域的指针
        int j=end; //右边区域的指针
        int[] temp= new int[end-start+1];  //临时区域
        int k=end-start; //临时区域的指针
        while(i>=start && j>=mid+1){
            if(array[i]>array[j]){
                count+=(j-mid);
                temp[k--]=array[i--];
            }else{
                temp[k--]=array[j--];
            }
        }
        while(i>=start)
            temp[k--]=array[i--];
        while(j>=mid+1)
            temp[k--]=array[j--];
        for(k=0;k<temp.length;k++)
            array[k+start]=temp[k];
         
        return count+left+right;
    }
}

        归并排序的时间复杂度是O(nlogn),比直观的O(n^2)要快,但同时归并排序需要一个长度为n的辅助数组,相当于用O(n)的空间消耗换来了时间效率的提升,因此这是一种空间换时间的算法。

猜你喜欢

转载自blog.csdn.net/qq_32534441/article/details/89214129
今日推荐