题目描述
小M是一个五年级的小学生,今天他学习了整除的知识,想通过一些练习来巩固自己的理解。他写下了一个长度为 n 的正整数序列 a_0, a_1, ..., a_{n-1},然后想知道有多少个连续子序列的和能够被一个给定的正整数 b 整除。你能帮小M解决这个问题吗?
输入
- 整数
n(1 ≤ n ≤ 10^5):序列的长度。 - 整数
b(1 ≤ b ≤ 10^9):整除的目标值。 - 长度为
n的正整数序列sequence,其中每个元素满足1 ≤ a_i ≤ 10^9。
输出
- 输出满足条件的子序列数量。
示例
输入 1:
n = 3, b = 3
sequence = [1, 2, 3]
输出 1:
3
输入 2:
n = 4, b = 5
sequence = [5, 10, 15, 20]
输出 2:
10
输入 3:
n = 5, b = 2
sequence = [1, 2, 3, 4, 5]
输出 3:
6
思路1:暴力枚举法(O(n^2))
最直观的做法是暴力枚举所有可能的连续子数组,计算其和,判断是否能被 b 整除。这种方法的步骤如下:
- 对于每一对
i和j(0 ≤ j ≤ i < n),枚举所有从sequence[j]到sequence[i]的子数组。 - 对每个子数组,计算其和,判断是否能够被
b整除。 - 累计满足条件的子数组数量。
时间复杂度:由于我们有两个嵌套的循环,外层循环遍历 i,内层循环遍历 j,每次计算一个子数组的和。时间复杂度是 O(n^2)。
缺点:当 n 较大时,这种方法效率较低,可能导致超时。
def solution(n, b, sequence):
count = 0
for i in range(n): # 外层循环
for j in range(i + 1): # 内层循环
if sum(sequence[j:i+1]) % b == 0: # 判断子数组和是否能被b整除
count += 1
return count
思路2:前缀和与模运算优化(O(n))
为了提升效率,可以使用 前缀和(Prefix Sum) 和 取模运算 来将时间复杂度从 O(n^2) 降低到 O(n)。
关键点:
- 前缀和:
prefix[i]表示从sequence[0]到sequence[i-1]的和。子数组[j, i-1]的和可以通过prefix[i] - prefix[j]来计算。 - 模运算:我们需要判断子数组和是否能被
b整除,等价于判断(prefix[i] - prefix[j]) % b == 0。利用模的性质可以转换为判断prefix[i] % b == prefix[j] % b。 - 使用哈希表记录前缀和的模值的出现次数,遇到相同的模值时,说明存在一个子数组和能够被
b整除。
时间复杂度:我们只需要遍历一次 sequence,每次更新前缀和并更新哈希表,时间复杂度为 O(n)。
from collections import defaultdict
def solution(n, b, sequence):
count = 0
prefix_sum = 0
mod_count = defaultdict(int)
# 初始化,prefix_sum = 0 mod b,表示空前缀的情况
mod_count[0] = 1
for num in sequence:
prefix_sum += num
mod_value = prefix_sum % b
# 处理负数模值情况,因为 Python 的模运算对负数会有负结果
if mod_value < 0:
mod_value += b
# 如果之前有相同模值的前缀和,那么说明有子序列的和能被 b 整除
count += mod_count[mod_value]
# 更新当前模值出现的次数
mod_count[mod_value] += 1
return count
总结
- 暴力枚举法:时间复杂度是 O(n^2),适用于小规模的
n,但是当n较大时会超时,因此不适合大数据量的情况。 - 前缀和与模运算优化:通过前缀和计算子数组的和,并利用模运算来判断是否能被
b整除,时间复杂度优化为 O(n),适用于大规模的数据。
最终推荐的解法是 前缀和与模运算优化法,因为它具有更高的效率,能够在较大的数据范围内处理问题。