问题描述
小M是一个五年级的小学生,今天他学习了整除的知识,想通过一些练习来巩固自己的理解。他写下了一个长度为 n 的正整数序列 a_0, a_1, ..., a_{n-1},然后想知道有多少个连续子序列的和能够被一个给定的正整数 b 整除。你能帮小M解决这个问题吗?
测试样例
样例1:
输入:
n = 3,b = 3,sequence = [1, 2, 3]
输出:3
样例2:
输入:
n = 4,b = 5,sequence = [5, 10, 15, 20]
输出:10
样例3:
输入:
n = 5,b = 2,sequence = [1, 2, 3, 4, 5]
输出:6
问题理解
我们需要计算一个长度为 n 的正整数序列中,有多少个连续子序列的和能够被一个给定的正整数 b 整除。
数据结构选择
为了高效地解决这个问题,我们可以使用以下数据结构:
- 前缀和数组:用于存储从序列开始到当前位置的和。
- 哈希表:用于存储前缀和的余数及其出现的次数。
算法步骤
-
初始化:
- 创建一个哈希表
prefixSumModCount,用于存储前缀和的余数及其出现的次数。 - 初始化前缀和
prefixSum为 0,并将prefixSumModCount中余数为 0 的计数初始化为 1。
- 创建一个哈希表
-
遍历序列:
- 对于序列中的每个元素
num,更新前缀和prefixSum。 - 计算当前前缀和
prefixSum对b的余数mod。 - 如果
mod为负数,将其调整为正数。
- 对于序列中的每个元素
-
查找和更新哈希表:
- 如果哈希表中已经存在相同的余数
mod,说明存在子数组的和可以被b整除,将这些子数组的数量累加到count中。 - 更新哈希表中当前余数
mod的计数。
- 如果哈希表中已经存在相同的余数
-
返回结果:
- 遍历结束后,返回
count,即满足条件的连续子序列的数量。
- 遍历结束后,返回
复杂度分析
- 时间复杂度:
O(n),因为我们只需要遍历一次序列。 - 空间复杂度:
O(n),主要用于存储哈希表中的前缀和余数。
代码实现
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
public class Main {
public static int solution(int n, int b, List<Integer> sequence) {
// 创建一个哈希表来存储前缀和的余数及其出现的次数
Map<Integer, Integer> prefixSumModCount = new HashMap<>();
// 初始化前缀和为0,余数为0的情况
prefixSumModCount.put(0, 1);
int prefixSum = 0;
int count = 0;
for (int num : sequence) {
prefixSum += num;
int mod = prefixSum % b;
// 如果mod为负数,调整为正数
if (mod < 0) {
mod += b;
}
// 如果哈希表中存在相同的余数,说明存在子数组的和可以被b整除
if (prefixSumModCount.containsKey(mod)) {
count += prefixSumModCount.get(mod);
}
// 更新哈希表中当前余数的计数
prefixSumModCount.put(mod, prefixSumModCount.getOrDefault(mod, 0) + 1);
}
return count;
}
public static void main(String[] args) {
// You can add more test cases here
List<Integer> sequence = new ArrayList<>();
sequence.add(1);
sequence.add(2);
sequence.add(3);
System.out.println(solution(3, 3, sequence) == 3);
}
}
总结
通过使用前缀和和哈希表,我们可以在 O(n) 的时间复杂度内解决这个问题,非常适合处理大规模数据。这种方法不仅高效,而且易于理解和实现,是解决此类问题的经典方法。
此外,与前缀和相对应的方法是差分,这是一种在O(n)时间内求和的方法,在未来也应该掌握。