1. 程式人生 > >【劍指Offer學習】【面試題36:陣列中的逆序對】

【劍指Offer學習】【面試題36:陣列中的逆序對】

題目:在陣列中的兩個數字如果前面一個數字大於後面的數字,則這兩個數字組成一個逆序對。輸入一個數組,求出這個陣列中的逆序對的總數。

舉例分析

  例如在陣列{7, 5, 6, 4 中, 一共存在5 個逆序對,分別是(7, 6)、(7,5),(7, 4)、(6, 4)和(5, 4)。

解題思路:

第一種:直接求解

  順序掃描整個陣列。每掃描到一個數字的時候,逐個比較該數字和它後面的數字的大小。如果後面的數字比它小,則這兩個數字就組成了一個逆序對。假設陣列中含有n 個數字。由於每個數字都要和O(n)個數字作比較, 因此這個演算法的時間複雜度是O(n^2)。

第二種:分析法

  我們以陣列{7, 5, 6, 4}為例來分析統計逆序對的過程。每次掃描到一個數字的時候,我們不能拿它和後面的每一個數字作比較,否則時間複雜度就是O(n^5),因此我們可以考慮先比較兩個相鄰的數字。

這裡寫圖片描述

  如圖5 . 1 ( a )和圖5.1 ( b)所示,我們先把陣列分解成兩個長度為2的子陣列, 再把這兩個子陣列分別拆分成兩個長度為1 的子陣列。接下來一邊合併相鄰的子陣列, 一邊統計逆序對的數目。在第一對長度為1 的子陣列{7}、{5}中7 大於5 , 因此(7, 5)組成一個逆序對。同樣在第二對長度為1 的子陣列{6}、{4}中也有逆序對(6, 4)。由於我們已經統計了這兩對子陣列內部的逆序對,因此需要把這兩對子陣列排序( 圖5.1 ( c)所示),以免在以後的統計過程中再重複統計。

這裡寫圖片描述

 圖中省略了最後一步, 即複製第二個子陣列最後剩餘的4 到輔助陣列中.
(a) P1指向的數字大於P2指向的數字,表明陣列中存在逆序對.P2 指向的數字是第二個子陣列的第二個數字, 因此第二個子陣列中有兩個數字比7 小. 把逆序對數目加2,並把7 複製到輔助陣列,向前移動P1和P3.
(b) P1指向的數字小子P2 指向的數字,沒有逆序對.把P2 指向的數字複製到輔助陣列,並向前移動P2 和P3 .
(c) P1指向的數字大於P2 指向的數字,因此存在逆序對. 由於P2 指向的數字是第二個子陣列的第一個數字,子陣列中只有一個數字比5 小. 把逆序對數目加1 ,並把5複製到輔助陣列,向前移動P1和P3 .

  接下來我們統計兩個長度為2 的子陣列之間的逆序對。我們在圖5.2 中細分圖5.1 ( d)的合併子陣列及統計逆序對的過程。
  我們先用兩個指標分別指向兩個子陣列的末尾,並每次比較兩個指標指向的數字。如果第一個子陣列中的數字大於第二個子陣列中的數字,則構成逆序對,並且逆序對的數目等於第二個子陣列中剩餘數字的個數(如圖5.2 (a)和圖5.2 (c)所示)。如果第一個陣列中的數字小於或等於第二個陣列中的數字,則不構成逆序對(如圖5.2 (b)所示〉。每一次比較的時候,我們都把較大的數字從·後往前複製到一個輔助陣列中去,確保輔助陣列中的數字是遞增排序的。在把較大的數字複製到輔助陣列之後,把對應的指標向前移動一位,接下來進行下一輪比較。
  經過前面詳細的詩論, 我們可以總結出統計逆序對的過程:先把陣列分隔成子陣列, 先統計出子陣列內部的逆序對的數目,然後再統計出兩個相鄰子陣列之間的逆序對的數目。在統計逆序對的過程中,還需要對陣列進行排序。如果對排序賀,法很熟悉,我們不難發現這個排序的過程實際上就是歸併排序。

程式碼實現:

public class Test36 {

    public static int inversePairs(int[] data) {
        if (data == null || data.length < 1) {
            throw new IllegalArgumentException("Array arg should contain at least a value");
        }

        int[] copy = new int[data.length];
        System.arraycopy(data, 0, copy, 0, data.length);

        return inversePairsCore(data, copy, 0, data.length - 1);
    }

    private static int inversePairsCore(int[] data, int[] copy, int start, int end) {

        if (start == end) {
            copy[start] = data[start];
            return 0;
        }

        int length = (end - start) / 2;
        int left = inversePairsCore(copy, data, start, start + length);
        int right = inversePairsCore(copy, data, start + length + 1, end);

        // 前半段的最後一個數字的下標
        int i = start + length;
        // 後半段最後一個數字的下標
        int j = end;
        // 開始拷貝的位置
        int indexCopy = end;
        // 逆序數
        int count = 0;

        while (i >= start && j >= start + length + 1) {
            if (data[i] > data[j]) {
                copy[indexCopy] = data[i];
                indexCopy--;
                i--;
                count += j - (start + length); // 對應的逆序數
            } else {
                copy[indexCopy] = data[j];
                indexCopy--;
                j--;
            }
        }

        for (; i >= start;) {
            copy[indexCopy] = data[i];
            indexCopy--;
            i--;
        }

        for (; j >= start + length + 1;) {
            copy[indexCopy] = data[j];
            indexCopy--;
            j--;
        }
        return count + left + right;
    }

    public static void main(String[] args) {
        int[] data = {1, 2, 3, 4, 7, 6, 5};
        System.out.println(inversePairs(data)); // 3
        int[] data2 = {6, 5, 4, 3, 2, 1};
        System.out.println(inversePairs(data2)); //  15
        int[] data3 = {1, 2, 3, 4, 5, 6};
        System.out.println(inversePairs(data3)); // 0
        int[] data4 = {1};
        System.out.println(inversePairs(data4)); // 0
        int[] data5 = {1, 2};
        System.out.println(inversePairs(data5)); // 0
        int[] data6 = {2, 1};
        System.out.println(inversePairs(data6)); // 1
        int[] data7 = {1, 2, 1, 2, 1};
        System.out.println(inversePairs(data7)); // 3
    }
}

執行結果

這裡寫圖片描述