连续子串和整除问题| 豆包MarsCode AI刷题

74 阅读6分钟

题目概述

小M是一个五年级的小学生,今天他学习了整除的知识,想通过一些练习来巩固自己的理解。他写下了一个长度为 n 的正整数序列 a_0, a_1, ..., a_{n-1},然后想知道有多少个连续子序列的和能够被一个给定的正整数 b 整除。你能帮小M解决这个问题吗?

解题思路详解

在面对计算能被给定数整除的连续子序列数量的问题时,我们可以采用一种基于前缀和与哈希表(字典)的高效方法。以下是详细的解题思路:

1. 引入前缀和数组

前缀和数组是一个重要的工具,它允许我们在常数时间内计算出任意连续子序列的和。具体地,对于给定的整数序列 sequence,我们可以构造一个前缀和数组 prefix_sum,其中 prefix_sum[i] 存储了从序列开头到第 i 个位置(包含第 i 个位置)的所有元素的累加和。

2. 利用哈希表记录余数信息

为了快速判断某个子序列的和是否能被给定的数 b 整除,我们可以利用哈希表(在Python中通常使用字典)来记录前缀和模 b 的余数及其出现的次数。这样做的好处是,当我们遍历序列并计算前缀和时,可以立即查找之前是否出现过相同的余数。如果出现过,说明存在之前的某个位置到当前位置的子序列和可以被 b 整除。

3. 遍历序列并更新状态

我们遍历整数序列 sequence,同时维护前缀和数组 prefix_sum 和哈希表 remainder_count。对于每个位置 i,我们计算前缀和 prefix_sum[i],然后计算其余数 remainder = prefix_sum[i] % b

  • 如果 remainder 为0,说明从序列开头到当前位置的子序列和能被 b 整除,我们直接将结果计数器 result 加1。
  • 接下来,我们查找哈希表 remainder_count 中是否存在相同的余数。如果存在,说明存在之前的某个位置 jj < i),使得 prefix_sum[j] % b == remainder。这意味着从位置 j+1 到位置 i 的子序列和 prefix_sum[i] - prefix_sum[j] 能被 b 整除(因为两者模 b 的余数相同,相减后余数仍为0)。我们将这些子序列的数量(即哈希表中该余数的值)加到 result 中。
  • 最后,我们更新哈希表 remainder_count,记录当前余数 remainder 出现的次数。

4. 返回最终结果

遍历完成后,result 中存储的是所有能被 b 整除的子序列的数量。我们返回该值作为最终结果。

5.代码详解

代码分块详解

函数定义与初始化

python复制代码
	def solution(n, b, sequence):

	    # 初始化前缀和数组和字典

	    prefix_sum = [0] * (n + 1)

	    remainder_count = {}

	    result = 0
  • def solution(n, b, sequence)::定义了一个名为 solution 的函数,它接受三个参数:n(序列的长度),b(需要被整除的数),以及 sequence(给定的整数序列)。
  • prefix_sum = [0] * (n + 1):初始化了一个长度为 n+1 的前缀和数组 prefix_sum,并将所有元素初始化为0。这里数组长度设置为 n+1 是为了方便计算,使得 prefix_sum[i] 表示从序列开头到第 i 个元素(包含)的累加和。
  • remainder_count = {}:初始化了一个空字典 remainder_count,用于存储每个前缀和模 b 的余数及其出现的次数。
  • result = 0:初始化结果计数器 result 为0,用于存储能被 b 整除的子序列的数量。

计算前缀和并处理余数

python复制代码
	# 计算前缀和

	    for i in range(1, n + 1):

	        prefix_sum[i] = prefix_sum[i - 1] + sequence[i - 1]

	        

	        # 计算当前前缀和的余数

	        remainder = prefix_sum[i] % b
  • for i in range(1, n + 1)::遍历序列 sequence,索引从1开始到 n 结束(因为 prefix_sum 和 sequence 的索引是对齐的,但 prefix_sum 的第一个元素是0,用于方便计算)。
  • prefix_sum[i] = prefix_sum[i - 1] + sequence[i - 1]:计算当前位置的前缀和,并将其存储在 prefix_sum[i] 中。
  • remainder = prefix_sum[i] % b:计算当前前缀和模 b 的余数,并存储在变量 remainder 中。

更新结果和字典

python复制代码
	# 如果余数为0,说明从开头到当前位置的子序列和能被b整除

	        if remainder == 0:

	            result += 1

	        

	        # 查找字典中是否存在相同的余数

	        if remainder in remainder_count:

	            result += remainder_count[remainder]

	        

	        # 更新字典

	        if remainder in remainder_count:

	            remainder_count[remainder] += 1

	        else:

	            remainder_count[remainder] = 1
  • if remainder == 0::如果余数为0,说明从序列开头到当前位置的子序列和能被 b 整除,因此将 result 加1。
  • if remainder in remainder_count::查找字典 remainder_count 中是否存在相同的余数。如果存在,说明之前已经计算过某个位置到某个更早位置之间的子序列和能被 b 整除,因此将这些子序列的数量(即字典中该余数的值)加到 result 中。
  • if remainder in remainder_count: 和 else::更新字典 remainder_count,记录当前余数 remainder 出现的次数。如果余数已经存在,则将其计数加1;如果余数不存在,则将其添加到字典中,并设置计数为1。

返回结果

python复制代码
	return result
  • return result:返回最终的结果,即能被 b 整除的子序列的数量。

测试用例

python复制代码
	if __name__ == "__main__":

	    # 测试用例

	    sequence = [1, 2, 3]

	    print(solution(3, 3, sequence) == 3)  # 应该输出 True

	    

	    sequence = [5, 10, 15, 20]

	    print(solution(4, 5, sequence) == 10)  # 应该输出 True(注意:这里通常不包括空子序列,但所有子序列和都能被5整除,因此结果为10个有效子序列)

	    

	    sequence = [1, 2, 3, 4, 5]

	    print(solution(5, 2, sequence) == 6)  # 应该输出 True(有6个子序列和能被2整除)
  • 这些测试用例用于验证 solution 函数的正确性。每个测试用例都调用 solution 函数,并检查其返回值是否与预期结果相等。如果相等,则输出 True;否则,输出 False(但由于这里使用了 == 运算符进行比较,并且预期结果是直接写在代码中的,所以实际上不会看到 False 的输出,除非函数实现有误)。

注意事项

  • 在实现过程中,需要注意前缀和数组的索引与序列索引之间的对应关系。通常,我们将前缀和数组的索引设置为从0开始,而序列索引从1开始(或者两者都从0或1开始,但要保持一致)。
  • 另外,根据问题的具体要求,可能需要考虑空子序列是否计入结果。在大多数情况下,空子序列不被视为有效的子序列,因此在计算结果时通常不考虑它。然而,在某些特殊情况下(如题目明确说明或需要特殊处理),可能需要将空子序列纳入考虑范围。