问题描述
小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
解题思路
给定序列中任意连续子序列之和
- 定义前
i个元素之和为pre_sum[i],因此,任意一个连续子序列之和为
- 要使
sum(i,j)能够被b整除,对公式进行变化可得
字典查询
dict{}:通过key访问字典,当key不存在时,会引发keyError异常。defaultdict():为字典提供一个默认值,当key不存在时,默认添加该key,int 对应 0,str 对应 空字符串,list 对应 [ ]。
统计同余的次数
- 遍历整个序列,计算每前i个元素的序列和与
b的模值,即pre_sum[i] % b。 - 使用哈希表(字典)
count来记录每个模值出现的次数。 - 当在位置
i处遇到某个模值value时,意味着之前所有与value相同的模值对应的位置都可以形成一个满足条件的子序列。
计算结果
- 对于每个模值
value,如果它在前面已经出现过count[value]次,那么以当前位置为结束位置,并且和能被b整除的子序列的个数就增加了count[value]。
算法流程
-
初始化:
- 创建一个
defaultdict(int)类型的哈希表count,用于统计每个模值出现的次数。 - 将
count[0]初始化为1,可以推算,第一次模b=0时不需要配对。
- 创建一个
-
遍历序列并计算前i个元素的序列和:
- 初始化
pre_sum为0,用于累加当前的前i个元素的序列和。 - 初始化结果
result为0。
- 初始化
-
更新哈希表并计算结果:
-
对于序列中的每个元素
num:- 更新前缀和:
pre_sum += num。 - 计算当前前缀和的模值:
value = pre_sum % b。 - 将
count[value]累加到结果result中。 - 更新
count[value]的计数:count[value] += 1。
- 更新前缀和:
-
-
返回结果: 最终,
result即为满足条件的连续子序列的个数。
代码实现
from collections import defaultdict
def solution(n, b, sequence):
# 用字典记录 所有前i个连续子序列之和 对b的模值 出现的次数
count = defaultdict(int)
count[0] = 1 # 初始时,模为0的次数定义为1,可以自己推算
pre_sum = 0
result = 0
for num in sequence:
# 更新当前的序列和
pre_sum += num
# 当前序列和 模b 的值
value = pre_sum % b
# 计算当前模值出现的次数,说明有多少个子序列的和能被b整除
result += count[value]
# 更新value的出现次数
count[value] += 1
return result
if __name__ == "__main__":
sequence = [1, 2, 3]
print(solution(3, 3, sequence) == 3)
算法复杂度分析
-
时间复杂度:
- 算法主要遍历了一次序列,时间复杂度为
O(n)。 - 在循环中,所有操作都是常数时间的。
- 总时间复杂度:
O(n)。
- 算法主要遍历了一次序列,时间复杂度为
-
空间复杂度:
- 使用了一个哈希表
count,最坏情况下,模值的数量为b。 - 总空间复杂度:
O(min(n, b))。
- 使用了一个哈希表
总结
通过使用公式解析和和哈希表,将问题的复杂度降低到O(n)。算法利用了同余的性质,将连续子序列和能被b整除的问题,转换为序列和模值相等的问题。这样,我们只需统计序列和模值相同的次数,即可求出满足条件的子序列数量。