问题描述
小F正在研究数组的子数组问题。给定一个整数数组 nums
和一个整数 k
,你需要找到数组中所有元素之和可以被 k
整除的非空子数组的数目。注意,子数组是数组中的连续部分。
测试样例
样例1:
输入:
nums = [4,5,0,-2,-3,1] ,k = 5
输出:
7
样例2:
输入:
nums = [1,2,3] ,k = 3
输出:
3
样例3:
输入:
nums = [-1,2,9] ,k = 2
输出:
2
思路分析
-
前缀和的概念:
- 前缀和是指从数组的开头到某个位置的所有元素之和。例如,对于数组
[a1, a2, a3, ..., an]
,其前缀和数组为[s1, s2, s3, ..., sn]
,其中si = a1 + a2 + ... + ai
。
- 前缀和是指从数组的开头到某个位置的所有元素之和。例如,对于数组
-
利用前缀和求解:
- 如果两个前缀和
s1
和s2
满足(s1 - s2) % k == 0
,那么从s2+1
到s1
的子数组之和就能被k
整除。 - 因此,我们可以使用前缀和来解决这个问题。具体来说,我们需要记录每个前缀和对
k
取模的结果,并统计这些结果的出现次数。
- 如果两个前缀和
-
处理负数情况:
- 在 JavaScript 中,模运算可能会产生负数结果。为了避免这种情况,如果模运算结果为负数,我们需要将其调整为正数。
具体步骤
- 初始化前缀和
prefixSum
为 0,并创建一个哈希表prefixSumModK
来存储前缀和对k
取模的结果及其出现次数。初始时,将prefixSumModK.set(0, 1)
设置为 1,表示前缀和为 0 的情况已经存在一次。 - 遍历数组
nums
,计算当前前缀和prefixSum
。 - 计算当前前缀和对
k
取模的结果modulus
,并确保其为非负数(即modulus < 0
时,modulus += k
)。 - 更新计数
count
,加上prefixSumModK.get(modulus)
的值(如果没有出现过,则取默认值 0)。 - 更新
prefixSumModK
中modulus
的计数。 - 返回最终的计数
count
。
代码详解
ini
function solution(nums, k) {
let count = 0;
let prefixSum = 0;
let prefixSumModK = new Map(); // 初始化前缀和对 k 取模的哈希表,包含 0 的位置为 1
prefixSumModK.set(0, 1); // 初始化时设置 0 的位置为 1
for (let num of nums) {
prefixSum += num;
let modulus = prefixSum % k;
if (modulus < 0) modulus += k; // 处理负数情况,确保模运算结果为非负数
count += (prefixSumModK.get(modulus) || 0); // 以当前位置为结束位置的子数组和可以被 k 整除的数量
prefixSumModK.set(modulus, (prefixSumModK.get(modulus) || 0) + 1); // 更新前缀和对 k 取模的值
}
return count;
}
// 测试代码
console.log(solution([4, 5, 0, -2, -3, 1], 5) === 7);
console.log(solution([1, 2, 3], 3) === 3);
console.log(solution([-1, 2, 9], 2) === 2);
涉及到的知识点
1. 前缀和
- 定义:前缀和是指从数组的开头到某个位置的所有元素之和。
- 用途 :通过前缀和可以快速计算任意子数组的和。如果两个前缀和的差值能被
k
整除,那么这两者的子数组之和也能被k
整除。
2. 模运算
- 定义:模运算是一种取余运算,通常用于确定一个数除以另一个数的余数。
- 处理负数 :在某些编程语言中,模运算可能会产生负数结果。为了保证模运算结果为非负数,需要进行适当的调整(如
if (modulus < 0) modulus += k
)。
3. 哈希表(Map)
- 定义:哈希表是一种数据结构,可以在常数时间内完成插入、删除和查找操作。
- 用途 :本题中使用哈希表来记录前缀和对
k
取模的结果及其出现次数,从而高效地统计满足条件的子数组数量。
4. 子数组
- 定义:子数组是原数组中的连续部分。
- 求解方法:通过前缀和的方法,可以快速找到所有符合条件的子数组。
5. 计数与统计
- 目的:统计满足条件的子数组数量。
- 实现方式:在遍历数组的过程中,通过更新哈希表中的计数来统计符合条件的子数组。
总结
通过青训营豆包MarsCode AI刷题,我学到了以下几点:
-
前缀和的概念及应用:
- 前缀和可以帮助我们快速计算任意子数组的和。这对于解决一些涉及子数组和的问题非常有用。
-
模运算的理解:
- 模运算是一种取余运算,用于确定一个数除以另一个数的余数。理解如何处理模运算中的负数结果对于编程非常重要。
-
哈希表的应用:
- 哈希表是一种高效的键值对存储结构,适用于频繁的插入、删除和查找操作。在这类问题中,哈希表可以帮助我们快速统计符合条件的子数组数量。
-
子数组的统计方法:
- 通过前缀和和哈希表的结合使用,可以有效地统计出满足特定条件的子数组数量。这种方法不仅效率高,而且易于理解和实现。
-
算法优化思路:
- 这类问题展示了如何将多个概念(前缀和、模运算、哈希表)结合起来,达到优化算法的目的。理解这种组合思维有助于解决更多复杂问题。 总而言之,通过刷题我不仅学会了如何应用前缀和和哈希表,还了解了如何处理模运算中的边界条件和负数情况。这些技能在处理其他类似的数组问题时也非常有用。