1. 程式人生 > 實用技巧 >四數相加

四數相加

1.問題描述

給定四個包含整數的陣列列表 A , B , C , D ,計算有多少個元組 (i, j, k, l) ,使得 A[i] + B[j] + C[k] + D[l] = 0

為了使問題簡單化,所有的 A, B, C, D 具有相同的長度 N,且 0 ≤ N ≤ 500 。所有整數的範圍在 -228 到 228 - 1 之間,最終結果不會超過 231 - 1 。

例如:

輸入:
A = [ 1, 2]
B = [-2,-1]
C = [-1, 2]
D = [ 0, 2]

輸出:
2

解釋:
兩個元組如下:

  1. (0, 0, 0, 1) -> A[0] + B[0] + C[0] + D[1] = 1 + (-2) + (-1) + 2 = 0
  2. (1, 1, 0, 0) -> A[1] + B[1] + C[0] + D[0] = 2 + (-1) + (-1) + 0 = 0

2.求解

雜湊表

  • 看到形如:A+B....+N=0的式子,要轉換為(A+...T)=-((T+1)...+N)再計算,這個T的分割點一般是一半,特殊情況下需要自行判斷。

    為什麼一般情況分割點是一半,例如本題的四個元組,分割方法有以下三種

    • HashMap 存一個數組,如 A。然後計算三個陣列之和,如 BCD。時間複雜度為:O(n)+O(n^3),得到 O(n^3).
    • HashMap 存三個陣列之和,如 ABC。然後計算一個數組,如 D。時間複雜度為:O(n^3)+O(n)
      ,得到 O(n^3).
    • HashMap存兩個陣列之和,如AB。然後計算兩個陣列之和,如 CD。時間複雜度為:O(n^2)+O(n^2),得到 O(n^2).

    因此,分割一半是最優事件複雜度。

  • 在本題中,可以先將兩個元組的各個元素相加的和存入hash表中,再遍歷另外兩個元組,看能否找到等於0的值

程式碼如下

	/*
    執行用時:69 ms, 在所有 Java 提交中擊敗了89.83% 的使用者
    記憶體消耗:56.6 MB, 在所有 Java 提交中擊敗了92.34% 的使用者
    */
    public int fourSumCount(int[] A, int[] B, int[] C, int[] D) {
        Map<Integer, Integer> map = new HashMap<>();
        for(int x : A){
            for(int y : B){
                map.put(x + y, map.getOrDefault(x + y, 0) + 1);
            }
        }
        int ans = 0;
        for(int x : C){
            for(int y : D){
                if(map.containsKey(-x -y)){
                    ans += map.get(-x -y);
                }
            }
        }
        return ans;
    }
  • 時間、空間複雜度均為O(n^2)

關於HashMap容量:

  • 可以在建立HashMap時傳入初始桶大小,例如在本題中可以建立大小為A.length * A.length的的HashMap。這樣可以避免因擴容而帶來的效能損耗。

    Map<Integer, Integer> map = new HashMap<>(A.length * A.length);

  • 在建立HashMap時不傳入初始桶大小,則預設桶大小為16

  • java 7中的擴容機制需滿足兩個條件

    1. 存放新值的時候當前已有元素的個數必須大於等於閾值
    2. 存放新值的時候當前存放資料發生hash碰撞(當前key計算的hash值換算出來的陣列下標位置已經存在值)

    因此可能會出現以下情況

    (1)、就是hashmap在存值的時候(預設大小為16,負載因子0.75,閾值12),可能達到最後存滿16個值的時候,再存入第17個值才會發生擴容現象,因為前16個值,每個值在底層陣列中分別佔據一個位置,並沒有發生hash碰撞。

    (2)、當然也有可能儲存更多值(超多16個值,最多可以存26個值)都還沒有擴容。原理:前11個值全部hash碰撞,存到陣列的同一個位置(雖然hash衝突,但是這時元素個數小於閾值12,並沒有同時滿足擴容的兩個條件。所以不會擴容),後面所有存入的15個值全部分散到陣列剩下的15個位置(這時元素個數大於等於閾值,但是每次存入的元素並沒有發生hash碰撞,也沒有同時滿足擴容的兩個條件,所以葉不會擴容),前面11+15=26,所以在存入第27個值的時候才同時滿足上面兩個條件,這時候才會發生擴容現象。

  • java 8的擴容機制

    (1)Java 8 在新增資料存入成功後進行擴容

    (2)擴容會發生在兩種情況下(滿足任意一種條件即發生擴容):

       a 當前存入資料大於閾值即發生擴容

       b 存入資料到某一條連結串列上,此時資料大於8,且總數量小於64即發生擴容

    (3)此外需要注意一點java7是在存入資料前進行判斷是否擴容,而java8是在存入資料庫在進行擴容的判斷。