JavaScript:leetcode_974. 和可被 K 整除的子數組(前序和 + 同餘定理)

題目說明

給定一個整數數組 A,返回其中元素之和可被 K 整除的(連續、非空)子數組的數目。

 

示例:

輸入:A = [4,5,0,-2,-3,1], K = 5
輸出:7
解釋:
有 7 個子數組滿足其元素之和可被 K = 5 整除:
[4, 5, 0, -2, -3, 1], [5], [5, 0], [5, 0, -2, -3], [0], [0, -2, -3], [-2, -3]
 

提示:

1 <= A.length <= 30000
-10000 <= A[i] <= 10000
2 <= K <= 10000

解題思路一(暴力枚舉,O(n^3))

  1. 暴力枚舉所有的前序和,判斷對K取模是否爲0,爲0則結果+1

代碼實現一

/**
 * @param {number[]} A
 * @param {number} K
 * @return {number}
 */
var subarraysDivByK = function(A, K) {
    let res = 0;
    for (let i = 0; i < A.length; i++) {
        let prev = 0;
        for (let j = i; j >= 0; j--) {
            prev += A[j];
            if (prev % K === 0) {
                res++
            }
        }
    }
    return res;
};

解題思路二(暴力枚舉優化,O(n^2))

  1. 將兩層for循環中的求前序和操作,提前求。
  2. 那麼我們求i之前的所有前序和就變成了,求p[i] - p[j] (j的範圍是 0 ~ i-1)
  3. 判斷p[i] - p[j]對K去模是否爲0,爲0則結果+1

代碼實現二

/**
 * @param {number[]} A
 * @param {number} K
 * @return {number}
 */
var subarraysDivByK = function(A, K) {
    let res = 0;
    let p = new Array(A.length);
    for (let i = 0; i < A.length; i++) {
        p[i] = A.slice(0, i+1).reduce((sum, item, key, arr) => {
            return sum += item;
        }, 0);
        for( let j = i - 1; j >= 0; j--) {
            if((p[i] - p[j]) % K === 0) {
                res++
            }
        }
        if(p[i] % K === 0) {
            res++
        }
    }
    return res;
};

解題思路三(同餘定理,O(n))

先理解一個數學問題, 假設a = 8,b = 13, 同時mod 5,那麼 a % 5 == 3,b % 5 == 3,即a % 5 == b % 5(b - a) % 5 == 0即對同一數取模相同的兩個值,其差值可整除該數。

  1. 將兩層for循環中的求前序和操作,提前求前序和序列p。
  2. 得到所有的前序和p之後,理解說明若 p[i] % K === p[j] % Kp[i] - p[j] % 5 === 0那麼ji就是我們求的一個目標子序列。
  3. 所以我們建立一個hash,用來存儲p序列取模之後的值。 hash的鍵值範圍是(0 ~ K -1)因爲是對K取餘,所以值只可能出現在該範圍中。
  4. 由於該hash的標記跟數組下標正好對應,所以hash就聲明爲一個數組。
  5. 以示例爲例
    1. 輸入:A = [4,5,0,-2,-3,1], K = 5
    2. p序列爲 [4, 9, 9, 7, 4, 5] 取模之後的序列爲[4, 4, 4, 2, 4, 0] 記錄到hash中
    3. hash = [1, 0, 1, 0, 4]
    4. 接下來就是排列組合的問題了,將hash列表中 > 1 的值進行計算 n * ( n - 1 ) / 2 取和
    5. 最後再加上hash[0]的個數,因爲hash[0]標記的是取模之後爲0的值的個數,本身就屬於目標子序列。
  6. 第五步我們是先求出hash表才計算個數,我們也可以在完善hash的同時計算。
    1. 比如p序列爲 [4, 9, 9, 7, 4, 5] 去模的過程中統計。
    2. 計算第1個取模,模值爲4,res += hash[4], hash[4]++,由於4是第1次出現所以目前res+=0
    3. 計算第2個取模,模值爲4,res += hash[4], hash[4]++,由於4是第2次出現所以目前res+=1,子序列下標範圍是[0,1]
    4. 計算第3個取模,模值爲4,res += hash[4], hash[4]++,由於4是第3次出現所以目前res+=2,子序列下標範圍是[0,1,2],[1,2]因爲4出現3次,所以第3個4可以和前兩個組合。
    5. 依次類推。

代碼實現三

/**
 * @param {number[]} A
 * @param {number} K
 * @return {number}
 */
var subarraysDivByK = function(A, K) {
    let hash = new Array(K).fill(0);
    let sum = 0;
    let res = 0;
    for (let i = 0; i < A.length; i++) {
        sum += A[i];
        let key = sum % K;
        key = key < 0 ? (key + K) : key; //處理負數的情況, (3 - (-2)) % 5 === 0
        res += hash[key];
        hash[key]++;
    }
    return res + hash [0];
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章