数组中的逆序对 剑指offer java

题目描述

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007

 

数组中的逆序对 剑指offer java

思路:

1、看到这个题目,我们的第一反应是顺序扫描整个数组。每扫描到一个数组的时候,逐个比较该数字和它后面的数字的大小。如果后面的数字比它小,则这两个数字就组成了一个逆序对。假设数组中含有n个数字。由于每个数字都要和O(n)这个数字比较,因此这个算法的时间复杂度为O(n^2)。 (   第一个数字要和n-1个比较,第二个和n-2个比较...倒数第二个要和最后一个比较,一共要比较num=1+2+3+...+(n-1)=O(n^2);

2、考虑到归并排序的思想,

例如,两个已经排序的数组,假设按照从小到大排列的。例如下面的{5,7}和{4,6},已经比较5>4,则不需要比较7和4了,可以减少至少一半的比较的次数。

数组中的逆序对 剑指offer java

(a) 把长度为4的数组分解成两个长度为2的子数组;
(b) 把长度为2的数组分解成两个成都为1的子数组;
(c) 把长度为1的子数组 合并、排序并统计逆序对
(d) 把长度为2的子数组合并、排序,并统计逆序对;
在上图(a)和(b)中,我们先把数组分解成两个长度为2的子数组,再把这两个子数组分别拆成两个长度为1的子数组。接下来一边合并相邻的子数组,一边统计逆序对的数目。在第一对长度为1的子数组{7}、{5}中7大于5,因此(7,5)组成一个逆序对。同样在第二对长度为1的子数组{6}、{4}中也有逆序对(6,4)。由于我们已经统计了这两对子数组内部的逆序对,因此需要把这两对子数组 排序 如上图(c)所示, 避免在以后的统计过程中再重复统计

合并子数组并统计逆序对的过程如下图如下图所示:

数组中的逆序对 剑指offer java

 

分别是:

1)初始tmp={0,0,0,0}  ,n=2,(7与5,6和4两个逆序对),i=0,mid=1,j=2  k=0

2)5与4比较,5>4, 执行 tmp[k++]=a[j++];,   所以tmp={4,0,0,0} 再执行 cnt+=mid-i+1; n=4 ,j=3 ,k=1

3)5与6比较,5<6, 执行 tmp[k++]=a[i++];所以tmp={4,5,0,0} ; i=1 ,mid不变,k=2

4)7与6比较,7>6,tmp[k++]=a[j++];,  tmp={4,5,6,0},再执行 cnt+=mid-i+1; cnt=5  j=4,k=3

5) while(i<=mid)
            tmp[k++]=a[i++];  所以 tmp={4,5,6,7} k=4,i=2;

 

核心代码:

if(a[i]<=a[j])
    tmp[k++]=a[i++];
else{
    tmp[k++]=a[j++];
    cnt+=mid-i+1;
    cnt%=1000000007;
}

 注意:main方法里面调用非静态方法时,需要new一个对象,因为静态方法静态类属于模板,非静态类和方法属于对象

测试用例:

 public static void main(String[] args) {
        CompareTest1 com=new CompareTest1();
        int []arr = {7,5,6,4};
        int num=com.InversePairs(arr);

        System.out.println("逆序对的数目为"+  num);
    }

 修改这一部分代码,可以显示打印出逆序对:

数组中的逆序对 剑指offer java

最终输出结果如下图: 

数组中的逆序对 剑指offer java

完整代码如下: 


public class CompareTest1 {
    int cnt;
    public int InversePairs(int [] array) {
        cnt=0;
        if(array!=null)
            mergeSortUpDown(array,0,array.length-1);
        return cnt;
    }
    /*
     * 归并排序(从上往下)
     */
    public void mergeSortUpDown(int[] a, int start, int end){
        if(start>=end)
            return ;
        int mid=(start+end)/2;
        mergeSortUpDown(a,start,mid);
        mergeSortUpDown(a,mid+1,end);
        merge(a,start,mid,end);

    }
    /*
     * 将一个数组中的两个相邻有序区间合并成一个
     */
    public void merge(int[] a, int start, int mid, int end) {
        int[] tmp=new int[end-start+1];
        int i =start,j=mid+1,k=0;
        while(i<=mid&&j<=end){
            if(a[i]<=a[j])
                tmp[k++]=a[i++];
            else{
                tmp[k++]=a[j++];
        
                cnt+=mid-i+1;
                cnt%=1000000007;//需要对cnt进行1000000007取模。不然逆序对数目会溢出
            }
        }

        while(i<=mid)
            tmp[k++]=a[i++];
        while(j<=end)
            tmp[k++]=a[j++];

        for(k=0;k<tmp.length;k++){
            a[start+k]=tmp[k];
        }
    }
    public static void main(String[] args) {
        CompareTest1 com=new CompareTest1();
        int []arr = {7,5,6,4};
        int num=com.InversePairs(arr);

        System.out.println(num);
    }
}


参考:Aurora1

图解排序算法(四)之归并排序

参考:数组中的逆序对(思路与实现)