提示:本文参考英雄哪里出来的文章,原文连接⭐算法入门⭐《前缀和》中等02 —— LeetCode 974. 和可被 K 整除的子数组
一、题目
给定一个整数数组 A,返回其中元素之和可被 K 整除的(连续、非空)子数组的数目。
样例输入: A = [4,5,0,-2,-3,1], K = 5
样例输出: 7
原题链接:
二、解题思路
1.暴力
代码如下:
class Solution {
public:
int subarraysDivByK(vector<int>& nums, int k) {
int n = nums.size();
if(nums.empty()){
return 0;
}
vector<int> sums = nums;
int count = 0;
sums[0] = nums[0];
for(int i = 1; i < n; i++) {
sums[i] = sums[i-1] + nums[i];
}
for(int i = 0; i < n; ++i) {
if(sums[i] % k == 0) {
count++;
}
}
for(int i = 0; i < n-1; i++) {
for(int j = i+1; j < n; j++) {
if((sums[j] - sums[i]) % k == 0) {
count++;
}
}
}
return count;
}
};
结果运行超时
发现条件限制为:
- 1 < = n u m s . l e n g t h < = 3 ∗ 1 0 4 1 <= nums.length <= 3 * 10^4 1<=nums.length<=3∗104
- − 1 0 4 < = n u m s [ i ] < = 1 0 4 -10^4 <= nums[i] <= 10^4 −104<=nums[i]<=104
- 2 < = k < = 1 0 4 2 <= k <= 10^4 2<=k<=104
输入的数据量比较大,暴力行不通,更换思路。
2.哈希表 + 逐一统计 (Hush未掌握)
思路:来自 和可被 K 整除的子数组
通常,涉及连续子数组问题的时候,我们使用前缀和来解决。
我们令 P [ i ] = n u m s [ 0 ] + n u m s [ 1 ] + … + n u m s [ i ] P[i]=nums[0]+nums[1]+…+nums[i] P[i]=nums[0]+nums[1]+…+nums[i]。那么每个连续子数组的和 s u m ( i , j ) sum(i,j) sum(i,j) 就可以写成 P [ j ] − P [ i − 1 ] P[j] - P[i-1] P[j]−P[i−1](其中 0 < i < j 0 < i < j 0<i<j)的形式。此时,判断子数组的和能否被 k k k 整除就等价于判断 ( P [ j ] − P [ i − 1 ] ) m o d k = = 0 (P[j] - P[i-1]) \bmod k == 0 (P[j]−P[i−1])modk==0,根据 同余定理,只要 P [ j ] m o d k = = P [ i − 1 ] m o d k P[j] \bmod k == P[i-1] \bmod k P[j]modk==P[i−1]modk,就可以保证上面的等式成立。
因此我们可以考虑对数组进行遍历,在遍历同时统计答案。当我们遍历到第 ii 个元素时,我们统计以 ii 结尾的符合条件的子数组个数。我们可以维护一个以前缀和模 kk 的值为键,出现次数为值的哈希表 record \textit{record} record,在遍历的同时进行更新。这样在计算以 ii 结尾的符合条件的子数组个数时,根据上面的分析,答案即为 [ 0... i − 1 ] [0...i-1] [0...i−1]中前缀和模 k k k 也为 P [ i ] m o d k P[i] \bmod k P[i]modk的位置个数,即 record [ P [ i ] m o d k ] \textit{record}[P[i] \bmod k] record[P[i]modk]。
最后的答案即为以每一个位置为数尾的符合条件的子数组个数之和。需要注意的一个边界条件是,我们需要对哈希表初始化,记录 record [ 0 ] = 1 \textit{record}[0] = 1 record[0]=1,这样就考虑了前缀和本身被 k k k 整除的情况。
代码如下:
class Solution {
public:
int subarraysDivByK(vector<int>& nums, int k) {
unordered_map<int, int> record = {{0, 1}};
int sum = 0, ans = 0;
for (int elem: nums) {
sum += elem;
// 注意 C++ 取模的特殊性,当被除数为负数时取模结果为负数,需要纠正
int modulus = (sum % k + k) % k;
if (record.count(modulus)) {
ans += record[modulus];
}
++record[modulus];
}
return ans;
}
};
3.复杂度分析
时间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 nums \textit{nums} nums 的长度。我们只需要从前往后遍历一次数组,在遍历数组的过程中,维护哈希表的各个操作均为 O ( 1 ) O(1) O(1),因此总时间复杂度为 O ( n ) O(n) O(n)。
空间复杂度: O ( m i n ( n , k ) ) O(min(n,k)) O(min(n,k)),即哈希表需要的空间。当 n ≤ k n \leq k n≤k 时,最多有 n n n 个前缀和,因此哈希表中最多有 n + 1 n+1 n+1 个键值对;当 n > k n > k n>k 时,最多有 k k k 个不同的余数,因此哈希表中最多有 k k k 个键值对。也就是说,哈希表需要的空间取决于 n n n 和 k k k 中的较小值。
三.总结
对哈希表的不了解给我在理解此题的路上造成了巨大的障碍,此题未解决,先告一段落;