劍指Offer-37-陣列中逆序對

Special__Yang發表於2018-08-10

題目

在陣列中的兩個數字,如果前面一個數字大於後面的數字,則這兩個數字組成一個逆序對。輸入一個陣列,求出這個陣列中的逆序對的總數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;
    }

總結

可以多結合分治的思路來做,總陣列的逆序數可以看做左右子陣列中各自的逆序數總和加上這兩個子陣列合並後逆序數。

相關文章