1. 程式人生 > >算法之數組和問題

算法之數組和問題

分析方法 2個 依次 inline n個元素 組成 exe 指向 system

算法題之數組和求解

數組和問題

? 加上給定一個數組和值x。設計一個算法使得如果數組中存在兩個元素的和為x,則輸出兩個元素的值組成的數組(不區分先後),否則輸出{-1, -1}。

? 分析:

  1. 最簡單的辦法,就是依次求每個元素與其他元素的和。這個就是經典的握手問題,不難得出其最壞時間復雜度為: \(\Theta\)(\(n^2\)) 這種指數級別的時間復雜度必然不是我們想要的,直接PASS
  2. 先做排序然後再進行查找: 假設使用前面已知的最快的排序算法,最壞時間復雜度為: \(\Theta\)(nlg(n))。之後可以使用二分查找法對每個針對每個元素查找 x - arr[i] 是否在數組中,此時時間最壞時間復雜度為: \(\Theta\)
    (nlg(n))。該算法實現的代碼如下:
private static int[] findSum(int[] arr, int sum) {
        // STEP1: 先對數組采用歸並排序進行排序
        mergeSort(arr, 0, arr.length);
        
        // STEP2: 遍歷數組,用二分查找法查找是否存在sum-arr[i]的元素
        for (int i=0; i<arr.length; i++) {
            int j = binarySearch(arr, 0, arr.length, sum - arr[i]);
            if
(j != -1) { if (j != i) { return new int[] {arr[i], arr[j]}; } else { // j = i的時候,則需要判斷j的左側和右側的值是否和j相等,相等則證明存在兩個元素 if (arr[j - 1] == arr[j]) { return new int[] {arr[i], arr[j - 1]}; } else
if (arr[j + 1] == arr[j]) { return new int[] {arr[i], arr[j + 1]}; } } } } return new int[]{-1, -1}; }

註意其中的mergeSort和binarySearch調用的是前一章節所指的代碼http://www.cnblogs.com/Kidezyq/p/8379267.html。


擴展

  • 其實對於求兩個元素的和有一種時間復雜度為:\(\Theta\)(n)的算法。該算法利用了桶排序的思想,借助Map的特殊數據結構。我們這裏以arr[i]為key,i為value。要判斷sum-arr[i]是否存在數組中,只要看map.get(sum-arr[i])是否為空即可。實現的代碼如下:
private static int[] findSumWithMap(int[] arr, int sum) {
        Map<Integer, Integer> map = new HashMap<>();
        // STEP1: 將數據入map
        for (int i = 0; i < arr.length; i++) {
            map.put(arr[i], i);
        }

        // STEP2: 開始判斷sum-arr[i]是否在map中 
        for (int i = 0; i < arr.length; i++) {
            // 因為遍歷順序同放入map的順序都是從前到後,所以如果存在多個同值元素,其最終會將後者的下標放入map,此時不影響判斷邏輯
            if (map.get(sum - arr[i]) != null && i != map.get(sum - arr[i])) {
                return new int[]{arr[i], sum - arr[i]};
            }
        }
        return new int[]{-1, -1};
    }
  • 其實對於上面的分析方法2,還有一個優化的方法,可以對排序後的數組在時間:\(\Theta\)(n)之內找到兩個和為指定值的算法。方法的思想還是二分查找法。首先取兩個下邊lowIndex和upIndex,最開始的時候lowIndex指向數組首元素,upIndex指向數組末尾元素。然後比對arr[lowIndex] + arr[upIndex]與sum的關系。根據比對的結果分別對lowIndex和upIdex進行移動。此時對於後續的整個查找過程只需要\(\Theta\)(n)時間即可。源代碼如下:
private static int[] findSumTwoSide(int[] arr, int sum) {
        // STEP1: 使用歸並排序對數組進行排序
        mergeSort(arr, 0, arr.length);
        
        // STEP2: 進行兩端查找
        int lowIndex = 0;
        int upIndex = arr.length - 1;
        while (upIndex > lowIndex) {
            if (arr[lowIndex] + arr[upIndex] == sum) {
                // 相等直接返回
                return new int[] {arr[lowIndex], arr[upIndex]};
            } else if (arr[lowIndex] + arr[upIndex] < sum) {
                // 小於左側右移
                lowIndex++;
            } else {
                // 大於右側左移
                upIndex--;
            }
        }
        return new int[] {-1, -1};
    }
  • 該題可以延伸至n個元素的和:

其解決思想就是分治了。將n個元素的規模依次降低,最終降到2個元素的和。這裏給出三個元素求和的例子,其他多維依次類推:

private static int[] findSumOf3Digits(int[] arr, int sum) {
        // STEP1:先調用歸並排序算法進行排序
        mergeSort(arr, 0, arr.length);
        
        // STEP2: 進行細化問題處理
        // 先申請一個數組來存儲排除一個元素後的數組元素組成的新的數組
        int[] leftArr = new int[arr.length - 1];    
        for (int i = 0; i < arr.length; i++) {
            // 復制arr[i]左側元素到數組起始位置
            if (i > 0) {
                System.arraycopy(arr, 0, leftArr, 0, i);
            }
            
            // 復制arr[i]右側元素到數組結尾位置
            if (i < arr.length - 1) {
                System.arraycopy(arr, i + 1, leftArr, i, arr.length - i - 1);
            }
            
            // 如果剩下兩個數的和滿足,則返回三個元素
            int[] leftIndexes = findSumTwoSide(leftArr, sum - arr[i]);
            if (!Arrays.equals(leftIndexes, new int[] {-1, -1})) {
                return new int[] {arr[i], leftIndexes[0], leftIndexes[1]};
            }
        }
        
        return new int[] {-1, -1, -1};
    }

算法之數組和問題