給定整數陣列 nums 和整數 k,回傳元素總和可被 k 整除的(非空)子陣列的數目。
範例:nums = [4, 5, 0, -2, -3, 1], k = 5
與 LC 523 的判斷存在性不同,本題需要計算個數。
關鍵觀察:設 prefix[j] - prefix[i] 是 k 的倍數,則 prefix[j] % k == prefix[i] % k(同餘)。
因此,問題轉化為:計算所有前綴和中,餘數相同的兩兩配對數(C(count, 2) = count * (count-1) / 2)。
count[r] 表示前綴和餘數為 r 的個數(初始 count[0] = 1,代表前綴和為 0 的空前綴)。sum,計算 rem = sum % k。rem 對應的配對數為 count[rem](之前出現過 rem 的次數),加入答案。count[rem]++。C++ 負數取模陷阱:C++ 中 (-2) % 5 = -2(向零取整),但數學意義上餘數應為正(-2 mod 5 = 3)。正確寫法:rem = ((sum % k) + k) % k,確保餘數始終非負。
C(count, 2) 而是累加 count?因為我們是線上計算——遍歷到 j 時,count[rem] 恰好是 j 之前出現過相同餘數的次數,每個都與 j 配對形成一個合法子陣列,所以直接加 count[rem] 即可(等效於最終計算 C(count, 2) 的累計版本)。
O(n),其中 n 為陣列長度。只需一次線性遍歷,每次 HashMap(或陣列)操作均為 O(1)。
若使用固定大小陣列(Solution2),常數因子更小,因為避免了 HashMap 的雜湊計算開銷。
1. Initialize count array/map with count[0] = 1 2. sum = 0, result = 0 3. For each x in nums: a. sum += x b. rem = ((sum % k) + k) % k // ensure non-negative c. result += count[rem] // count previous prefixes with same remainder d. count[rem] += 1 4. Return result
雙重迴圈枚舉所有子陣列,用前綴和 O(1) 計算子陣列和,再判斷是否整除 k。
先計算所有 n+1 個前綴和(含空前綴),對每個前綴和取模,最後統計各餘數的出現次數,用 C(count[r], 2) 計算配對數。
同方案二,但在第二步用桶(大小為 k 的陣列)替代 HashMap,最後遍歷所有桶計算 C(count[r], 2) 並求和。
子陣列和恰好為 k(不是倍數):這是 LC 560(Subarray Sum Equals K),同樣用前綴和 + HashMap,但不需要取模,直接用 count[sum - k] 計算,是本題的簡化版本。
判斷是否存在(非計數):若只需判斷是否存在長度 ≥ 2 的子陣列和為 k 的倍數(LC 523),HashMap 中只記錄最早出現的索引即可,無需計數;當找到相同餘數且距離 ≥ 2 時即回傳 true。
負數取模的通用處理:在 Python 中 -2 % 5 = 3(正餘數),而在 C++/Java 中 -2 % 5 = -2(向零取整)。通用公式 ((x % k) + k) % k 在任何語言中都能確保非負餘數,是處理「負數前綴和取模」問題的標準寫法,必須熟記。