刷题解析:连续子串和的整除问题--Python | 豆包MarsCode AI刷题

107 阅读3分钟

题目难度⭐️⭐️

原题:

问题描述

小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

问题分析:

去除题目背景就变成: 给定一个长度为 n 的正整数序列,以及一个正整数 b,我们需要找出所有连续子序列的和能够被 b 整除的情况。

关键概念:

  • 连续子序列:指的是序列中相邻的元素组成的子集。例如,序列 [1, 2, 3, 4] 的连续子序列有 [1], [2], [3], [4], [1, 2], [2, 3], [3, 4], [1, 2, 3], [2, 3, 4], [1, 2, 3, 4] 等。

  • 前缀和:这是一个有效的技巧,可以用来快速计算任意子序列的和。前缀和数组 prefix_sum 的定义是 prefix_sum[i] 表示从序列开始到索引 i-1 的和。可以通过 prefix_sum[j] - prefix_sum[i-1] 计算子序列 sequence[i]sequence[j] 的和。

  • 模运算:我们关心的是子序列的和是否能被 b 整除。因此,实际上只需要关注前缀和的模 b 的值。若两次前缀和的模值相同,则它们之间的子序列和能够被 b 整除。

算法步骤:

  • 计算前缀和:创建一个数组 prefix_sum 来存储前缀和。

  • 利用哈希表:使用哈希表(字典)记录每个前缀和模 b 的值出现的次数。当当前前缀和模值在哈希表中已存在时,说明可以找到一个或多个之前的前缀和,使得两者之间的子数组和能够被 b 整除。

  • 更新计数:每次遇到新的前缀和模值时,更新计数器,并将当前模值记录到哈希表中。

示例分析:

序列 [1, 2, 3],对 b=3,连续子序列的和包括:

  • [1] 和 1 不能整除 3。
  • [2] 和 2 不能整除 3。
  • [3] 和 3 能整除 3。
  • [1, 2] 和 3 能整除 3。
  • [2, 3] 和 5 不能整除 3。
  • [1, 2, 3] 和 6 能整除 3。

所以总共有 3 个子序列的和能够被 3 整除。

代码:

def solution(n, b, sequence):
    count = 0
    # 创建一个前缀和数组
    prefix_sum = [0] * (n + 1)

    for i in range(1, n + 1):
        prefix_sum[i] = prefix_sum[i - 1] + sequence[i - 1]

    # 使用字典来记录前缀和对 b 取模的结果的频率
    mod_count = {}
    
    for i in range(n + 1):
        mod_value = prefix_sum[i] % b
        if mod_value < 0:  # 确保模值为正
            mod_value += b
            
        # 如果 mod_value 已经存在于字典中,说明可以找到以当前索引为结尾的
        # 子数组,其和能被 b 整除
        if mod_value in mod_count:
            count += mod_count[mod_value]
        
        # 记录当前前缀和的 mod 值
        if mod_value in mod_count:
            mod_count[mod_value] += 1
        else:
            mod_count[mod_value] = 1

    return count

if __name__ == "__main__":
    #  You can add more test cases here
    sequence = [1, 2, 3]
    print(solution(3, 3, sequence) == 3)