问题理解
我们需要计算一个长度为 n 的正整数序列中,有多少个连续子序列的和能够被一个给定的正整数 b 整除。这个问题可以转化为寻找满足特定条件的子序列数量,即子序列的和是 b 的倍数。
数据结构选择
- 前缀和:为了快速计算任意子序列的和,我们可以使用前缀和数组。前缀和数组的第
i个元素表示原数组前i个元素的和。 - 哈希表:为了高效地查找和更新前缀和的余数,我们可以使用哈希表来存储每个余数的出现次数。
算法步骤
-
初始化:
- 创建一个哈希表
prefix_sum_mod,用于存储前缀和的余数及其出现次数。初始化prefix_sum_mod[0] = 1,因为余数为0的情况至少出现一次。 - 初始化
prefix_sum为0,用于记录当前的前缀和。 - 初始化
count为0,用于记录满足条件的子序列数量。
- 创建一个哈希表
-
遍历序列:
- 对于序列中的每个元素
num,更新prefix_sum为prefix_sum + num。 - 计算当前前缀和
prefix_sum除以b的余数mod。 - 如果
mod在哈希表中已经存在,说明存在一个子序列的和可以被b整除,将对应的子序列数量加到count中。 - 更新哈希表
prefix_sum_mod,将mod的出现次数加1。
- 对于序列中的每个元素
-
返回结果:
- 遍历结束后,返回
count,即满足条件的子序列数量。
- 遍历结束后,返回
具体步骤解析
-
初始化:
- 我们首先创建一个哈希表
prefix_sum_mod,用于存储前缀和的余数及其出现次数。哈希表的键是余数,值是该余数出现的次数。初始化prefix_sum_mod[0] = 1,因为余数为0的情况至少出现一次。 - 初始化
prefix_sum为0,用于记录当前的前缀和。prefix_sum表示从序列的第一个元素到当前元素的和。 - 初始化
count为0,用于记录满足条件的子序列数量。count最终将是我们需要返回的结果。
- 我们首先创建一个哈希表
-
遍历序列:
- 我们遍历序列中的每个元素
num,更新prefix_sum为prefix_sum + num。这样,prefix_sum始终表示从序列的第一个元素到当前元素的和。 - 计算当前前缀和
prefix_sum除以b的余数mod。余数mod表示当前前缀和除以b后的余数。 - 如果
mod在哈希表prefix_sum_mod中已经存在,说明存在一个子序列的和可以被b整除。具体来说,如果prefix_sum_mod[mod]大于0,说明之前存在一个前缀和的余数与当前前缀和的余数相同,这意味着这两个前缀和之间的子序列的和是b的倍数。我们将prefix_sum_mod[mod]的值加到count中。 - 更新哈希表
prefix_sum_mod,将mod的出现次数加1。这样,我们可以在后续的遍历中继续使用这个信息。
- 我们遍历序列中的每个元素
-
返回结果:
- 遍历结束后,返回
count,即满足条件的子序列数量。count记录了所有满足条件的子序列的数量。
- 遍历结束后,返回
复杂度分析
- 时间复杂度:O(n),因为我们只需要遍历一次序列。每次遍历中,我们进行常数时间的操作,包括更新前缀和、计算余数、查找和更新哈希表。
- 空间复杂度:O(n),哈希表最多存储
n个不同的余数。在最坏情况下,每个前缀和的余数都不同,因此哈希表的大小为n。
总结
通过使用前缀和和哈希表,我们可以在 O(n) 的时间复杂度内解决这个问题,有效地避免了暴力解法的 O(n^2) 时间复杂度。前缀和数组帮助我们快速计算任意子序列的和,而哈希表则帮助我们高效地查找和更新前缀和的余数。通过这种方法,我们可以准确地计算出满足条件的子序列数量。