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

82 阅读3分钟

问题描述

给定一个长度为 nn 的正整数序列 a0,a1,,an1a_0, a_1, \ldots, a_{n-1} 和一个正整数 bb,要求找出有多少个连续子序列的和能够被 bb 整除。

样例

样例1

  • 输入:n=3n = 3, b=3b = 3, 序列 = [1, 2, 3]
  • 输出:3
  • 解释:符合条件的子序列有 [1, 2], [1, 2, 3], [3]

样例2

  • 输入:n=4n = 4, b=5b = 5, 序列 = [5, 10, 15, 20]
  • 输出:10
  • 解释:符合条件的子序列有 [5], [10], [15], [20], [5, 10], [10, 15], [15, 20], [5, 10, 15], [10, 15, 20], [5, 10, 15, 20]

样例3

  • 输入:n=5n = 5, b=2b = 2, 序列 = [1, 2, 3, 4, 5]
  • 输出:6
  • 解释:符合条件的子序列有 [1, 2], [3, 4], [1, 2, 3, 4], [1], [3], [5]

解题思路

前缀和与模运算

为了高效地解决这个问题,我们可以利用前缀和的概念和模运算的性质。具体来说,如果一个子序列的和能够被 bb 整除,那么这个子序列的和模 bb 的结果为 0。我们可以通过计算前缀和的模 bb 来简化这个问题。

关键点

  1. 前缀和:定义 SiS_i 为从序列开始到第 ii 个元素的和,即 Si=a0+a1++aiS_i = a_0 + a_1 + \ldots + a_i
  2. 模运算:如果一个子序列的和 SjSiS_j - S_i 能够被 bb 整除,那么 (SjSi)%b=0(S_j - S_i) \% b = 0,这等价于 Sj%b=Si%bS_j \% b = S_i \% b

算法步骤

  1. 初始化

    • 创建一个数组 prefix_sum_mod 用于存储每个前缀和模 bb 的结果。
    • 创建一个哈希表 mod_count 用于记录每个模值出现的次数。
    • 初始化变量 total_subsequences 用于记录满足条件的子序列数量。
  2. 计算前缀和并取模

    • 遍历序列,计算当前前缀和 current_sum,并将其模 bb 的结果存储在 prefix_sum_mod 中。
    • 更新哈希表 mod_count,记录每个模值出现的次数。
  3. 计算满足条件的子序列数量

    • 遍历哈希表 mod_count,对于每个模值 xx 出现 cc 次,可以形成 c(c1)2\frac{c(c-1)}{2} 个子序列。
    • 特别地,如果某个前缀和的模值为 0,那么它本身也可以作为一个满足条件的子序列。

代码实现

def solution(n, b, sequence):
    # 初始化前缀和数组和哈希表
    prefix_sum_mod = [0] * n
    mod_count = {0: 1}  # 初始化模0的计数为1,因为前缀和为0的情况也算一个子序列
    total_subsequences = 0
    
    # 计算前缀和并取模
    current_sum = 0
    for i in range(n):
        current_sum += sequence[i]
        mod_value = current_sum % b
        
        # 更新哈希表
        if mod_value in mod_count:
            mod_count[mod_value] += 1
        else:
            mod_count[mod_value] = 1
    
    # 计算满足条件的子序列数量
    for count in mod_count.values():
        total_subsequences += (count * (count - 1)) // 2  # 组合的情况
    
    return total_subsequences

if __name__ == "__main__":
    # 测试样例
    sequence1 = [1, 2, 3]
    print(solution(3, 3, sequence1) == 3)
    
    sequence2 = [5, 10, 15, 20]
    print(solution(4, 5, sequence2) == 10)
    
    sequence3 = [1, 2, 3, 4, 5]
    print(solution(5, 2, sequence3) == 6)

代码解释

  1. 前缀和数组prefix_sum_mod 存储每个前缀和模 bb 的结果。
  2. 哈希表mod_count 记录每个模值出现的次数,初始时模0的计数为1。
  3. 组合公式:对于每个模值 xx 出现 cc 次,可以形成 c(c1)2\frac{c(c-1)}{2} 个子序列。
  4. 特殊情况:如果某个前缀和的模值为 0,那么它本身也是一个满足条件的子序列。

时间复杂度

  • 时间复杂度O(n)O(n),其中 nn 是序列的长度。我们只需要遍历一次序列来计算前缀和和更新哈希表。
  • 空间复杂度O(b)O(b),哈希表的大小取决于模值的数量,最大为 bb