🔗 🟡 974. Subarray Sums Divisible by K 1676

tags: Weekly Contest 119 前綴和(Prefix Sum) 雜湊表(Hash Table) 同餘定理

題意

給定一個整數陣列 numsnums 和一個整數 kk,返回具有可以被 kk 整除的和的 非空子陣列 的數量。

子陣列 是陣列的連續部分。

約束條件

  • 1nums.length31041 \leq \text{nums.length} \leq 3 \cdot 10^4
  • 104nums[i]104-10^4 \leq \text{nums}[i] \leq 10^4
  • 2k1042 \leq k \leq 10^4

思路:前綴和(Prefix Sum) + 雜湊表(Hash Table) + 同餘定理

我們可以使用 前綴和(Prefix Sum) 來計算陣列中每個位置的前綴和,對於子陣列 [l,r][l, r] 的和可以表示為 s[r]s[l1]s[r] - s[l-1],若其為 kk 的倍數,則 s[r]s[l1](modk)s[r] \equiv s[l-1] \pmod{k}

故可以記錄每個前綴和對 kk 取模後的結果,並使用一個雜湊表或長度為 kk 的陣列 cntcnt 來記錄取模後的值出現的次數。當枚舉到第 ii 個位置時,我們可以計算前 ii 個位置的前綴和 ss,並對 kk 取模,則 cnt[smodk]cnt[s \bmod k] 代表前面有多少個位置與當前位置的前綴和對 kk 取模後的結果相同,故能組成需要的子陣列,可以累加其出現次數至答案中。

具體步驟如下:

  1. 使用一個雜湊表或長度為 kk 的前綴和陣列 cntcnt 來記錄每個前綴和餘數出現的次數,初始化時設置 cnt[0]=1cnt[0] = 1,代表空陣列。
  2. 遍歷陣列 numsnums,對每一個元素累加計算前綴和 ss
  3. 計算當前前綴和 sskk 的餘數 smodks \bmod k
    • 由於我們只在意餘數,因此可以每次都將 sskk 取模。
    • 此外,由於約束條件中 nums[i]nums[i] 可以為負數,而在 C++ 中,對負數取模的結果可能為負數,因此我們需要對取模後的結果加上 kk 並再次對 kk 取模,以確保結果為正數。
  4. 將當前餘數出現的次數累加到答案中,這表示以當前元素為結尾的子陣列中,有多少個子陣列是可以被 kk 整除的。
  5. 更新雜湊表 cntcnt 中當前餘數出現的次數。

複雜度分析

  • 時間複雜度:O(n)\mathcal{O}(n),其中 nn 為陣列的長度。
  • 空間複雜度:O(min(n,k))\mathcal{O}(min(n, k)),雜湊表的空間複雜度。
    • 若使用長度為 kk 的陣列 cntcnt 來記錄,則空間複雜度為 O(k)\mathcal{O}(k)
1
2
3
4
5
6
7
8
9
10
11
class Solution:
def subarraysDivByK(self, nums: List[int], k: int) -> int:
ans = 0
s = 0 # prefix sum
cnt = defaultdict(int)
cnt[0] = 1
for x in nums:
s += x
ans += cnt[s % k]
cnt[s % k] += 1
return ans
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public:
int subarraysDivByK(vector<int>& nums, int k) {
int ans = 0, s = 0;
unordered_map<int, int> cnt;
cnt[0] = 1;
for (int x : nums) {
s = (s + (x % k) + k) % k; // 避免出現負數
ans += cnt[s];
cnt[s]++;
}
return ans;
}
};

寫在最後

Cover photo is generated by @ゴリラの素材屋さん, thanks for their work!