问题描述
小M是一个五年级的小学生,今天他学习了整除的知识,想通过一些练习来巩固自己的理解。他写下了一个长度为 n 的正整数序列 a_0, a_1, ..., a_{n-1},然后想知道有多少个连续子序列的和能够被一个给定的正整数 b 整除。你能帮小M解决这个问题吗?
问题理解
我们需要找出给定序列中,有多少个连续子序列的和能够被一个给定的正整数 b 整除。
数据结构选择
为了高效地解决这个问题,我们可以使用前缀和的概念。前缀和数组 prefixSum 记录从序列开始到当前位置的和。
算法步骤
- 计算前缀和:遍历序列,计算每个位置的前缀和。
- 利用前缀和的性质:如果两个前缀和
prefixSum[i]和prefixSum[j]对b取模的结果相同,那么prefixSum[j] - prefixSum[i]就是b的倍数。 - 使用哈希表:我们可以使用一个哈希表来记录每个前缀和对
b取模的结果出现的次数。这样,当我们遍历到某个前缀和时,可以直接从哈希表中获取之前有多少个相同取模结果的前缀和,从而快速计算出满足条件的子序列数量。
具体步骤
- 初始化一个哈希表
modCount,用于记录前缀和对b取模的结果出现的次数。 - 遍历序列,计算前缀和,并对
b取模。 - 将当前前缀和对
b取模的结果加入哈希表,并累加之前相同取模结果的次数到结果中。 - 返回结果。
通过这种方式,我们可以在 O(n) 的时间复杂度内解决这个问题。
代码实现 // 初始化一个哈希表,用于记录前缀和对 b 取模的结果出现的次数 Map<Integer, Integer> modCount = new HashMap<>(); // 初始化前缀和为 0,并将其对 b 取模的结果放入哈希表 int prefixSum = 0; modCount.put(prefixSum % b, 1);
int count = 0;
// 遍历序列
for (int num : sequence) {
// 计算当前前缀和
prefixSum += num;
// 计算当前前缀和对 b 取模的结果
int mod = prefixSum % b;
// 如果 mod 为负数,将其转换为正数
if (mod < 0) {
mod += b;
}
// 如果哈希表中已经存在相同取模结果的前缀和,累加到结果中
if (modCount.containsKey(mod)) {
count += modCount.get(mod);
}
// 将当前前缀和对 b 取模的结果放入哈希表
modCount.put(mod, modCount.getOrDefault(mod, 0) + 1);
}
return count;