剑指Offer-37-数组中逆序对

题目

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

解析

预备知识

逆序对:如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。

思路一

暴力解法,遍历数组,对于每一个数组元素,都要统计其剩余数组元素中比他小的元素个数,最后加在一起即为结果,复杂度O(n^2).

    private static final int MOD = (int) (1e9 + 7);
    public static int InversePairs(int [] array) {
        if(array == null || array.length == 0) {
            return 0;
        }
        int count = 0;
        for(int i = 0; i < array.length; i++) {
            for(int j = i + 1; j < array.length; j++) {
                if(array[i] > array[j]) {
                    count = (count + 1) % MOD;
                }
            }
        }
        return count;
    }

思路二

从逆序对的概念入手,一个在前的元素大于一个在后的元素即可组成逆序对。而一个排序好的数组逆序对数为0。
所以从排序角度来看,我们只要将每一个逆序对转换为正序,最后数组则是有序的。问题转换为如何利用排序统计逆序数。我们发现将一个逆序变为正序,只需交换两者即可,如果仅仅这样统计交换次数是不够。比如5432,对于5,2这对逆序数,我们交换5,2后,数组序列变为2435,这样我们会丢失中间4,2和3,2的逆序对。所以我们必须比如相邻元素来统计交换次数。 那么基于交换相邻元素的排序算法有冒泡排序,插入排序和归并排序(两个子数组合并可看做2个相邻交换,只不过需要特殊处理)。以下是插入排序代码:

    /**
     * 插入排序思想
     * @param array
     * @return
     */
    public static int InversePairs2(int [] array) {
        if(array == null || array.length == 0) {
            return 0;
        }
        int count = 0;
        for(int i = 1; i < array.length; i++) {
            for(int j = i; j > 0 && array[j] < array[j - 1]; j--) {
                count = (count + 1) % MOD;
                int temp = array[j];
                array[j] = array[j - 1];
                array[j - 1] = temp;
            }
        }
        return count;
    }

思路三

因为插入排序的复杂度为O(n^2),所以尝试利用归并排序来做。
举个例子4231,我们的思路用下图表示:
这里写图片描述
1. 我们采用自底向上的归并方法,不断将数组分为2个子数组
2. 直到子数组长度为1,对于长度为1子数组逆序数肯定为0
3. 开始合并子数组,对于2个长度为1的子数组,在合并时我们只需判断左边是否大于右边即可统计逆序数,那么对于大于1的子数组的合并怎么统计逆序数呢?我们采用以下通用的步骤进行合并子数组中逆序对数的统计。

这里假设我们对2,4和1,3两个子数组合并,两个指针(index1,index2)分别指向各自子数组的首部,同时申请一个辅助数组用于存放合并后数组(这一步与归并排序一样)
这里写图片描述
我们判断index1指向的元素是否大于index2指向的元素,这里2大于1,所以产生一个逆序数,并且我们发现如果左边的子数组中2大于1的话,它剩余元素都应该是大于1的,因为子数组是有序的。所以此时逆序对数为:左边剩余元素个数,也是2个。最后我们把1放到辅助数组中。
这里写图片描述
继续判断index1指向的元素是否大于index2指向的元素。这时2是小于3的,所以不存在逆序数,直接把2放到辅助数组中即可。
这里写图片描述
继续判断index1指向的元素是否大于index2指向的元素。这时4大于3,逆序数为左边子数组剩余的个数,也就是1 。最后把3放到辅助数组中。
这里写图片描述
这时右边子数组已经没有元素了,所以直接把左边子数组中剩余元素依次放到辅助数组即可。
这里写图片描述
以上,我们已经完成了2,4和1,3两个子数组合并,并统计了该过程中所有的逆序数。

    public static int InversePairs3(int [] array) {
        if(array == null || array.length == 0) {
            return 0;
        }
        return merge(array, 0, array.length - 1);
    }

    public static int merge(int[] array, int start, int end) {
        if(start == end) {
            return 0;
        }
        int mid = start + ((end - start) >> 1);
        int left = merge(array, start, mid);
        int right = merge(array, mid + 1, end);
        int[] aux = new int[end - start + 1];
        int index1 = start, index2 = mid + 1, count = 0, index = 0;
        while(index1 <= mid && index2 <= end) {
            if(array[index1] > array[index2]) {
                count = (count + mid - index1 + 1) % MOD;
                aux[index++] = array[index2++];
            } else {
                aux[index++] = array[index1++];
            }
        }
        while(index1 <= mid) {
            aux[index++] = array[index1++];
        }
        while(index2 <= end) {
            aux[index++] = array[index2++];
        }
        System.arraycopy(aux, 0, array, start, aux.length);
        return (left + right + count) % MOD;
    }

总结

可以多结合分治的思路来做,总数组的逆序数可以看做左右子数组中各自的逆序数总和加上这两个子数组合并后逆序数。

猜你喜欢

转载自blog.csdn.net/dawn_after_dark/article/details/81546391