青训营 连续子串和的整除问题刷题思路

105 阅读3分钟

连续子串和的整除问题

问题描述

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

解题思路

为了解决这个问题,我们需要计算所有连续子序列的和,并检查其中能被给定正整数 b 整除的子序列个数。为了提高效率,我们采用以下方法:

  1. 前缀和:通过维护一个前缀和数组,可以快速计算任意子序列的和。
  2. 取模:如果任意两个前缀和的差值可以被 b 整除,则它们对应的子序列和也可以被 b 整除。
  3. 哈希表:使用哈希表记录前缀和对 b 的模值出现的次数,快速查询已有的前缀和的模值。

具体来说,通过前缀和累加,计算当前前缀和对 b 取模的值。如果这个模值曾经出现过,则说明从之前的某个位置到当前的位置之间的子序列和可以被 b 整除。

代码实现

python
複製程式碼
def solution(n, b, sequence):
    # 前缀和
    prefix_sum = 0
    count = 0
    # 记录前缀和模 b 的次数
    mod_count = {0: 1}  # mod 0 出现 1 次(代表前缀和为0的情况)

    for num in sequence:
        prefix_sum += num
        mod_value = prefix_sum % b
        
        # 由于 Python 的 % 可能返回负值,我们需要调整它为非负
        if mod_value < 0:
            mod_value += b
        
        # 如果这个模值已经在 map 中,说明有多少个前缀和的差为 b 的倍数
        if mod_value in mod_count:
            count += mod_count[mod_value]
        
        # 更新 map 中这个模值的出现次数
        mod_count[mod_value] = mod_count.get(mod_value, 0) + 1

    return count

# 测试用例
if __name__ == "__main__":
    print(solution(3, 3, [1, 2, 3]))  # 输出: 3
    print(solution(4, 5, [5, 10, 15, 20]))  # 输出: 10
    print(solution(5, 2, [1, 2, 3, 4, 5]))  # 输出: 6

代码解释

  1. 前缀和:在每次迭代中,我们累加当前数字到 prefix_sum 中,计算当前子序列的和。
  2. 模值:计算当前前缀和对 b 的模值。由于 Python 的 % 可能返回负值,我们需要将模值调整为非负数。
  3. 哈希表:在哈希表中记录每个模值出现的次数。如果某个模值已经存在,则意味着从之前的某个位置到当前位置之间的子序列和能被 b 整除。
  4. 更新次数:每次计算完当前模值后,更新哈希表中的出现次数。

时间复杂度

该算法的时间复杂度为 O(n),其中 n 是序列的长度。通过使用前缀和和哈希表,我们能够高效地解决问题,避免了直接暴力计算每个子序列和的低效方法。

思考与收获

今天的实验让我深刻意识到,设计不仅仅是技术任务,更是一种在复杂需求中平衡效率、完整性和扩展性的艺术。在实际操作中,每个设计决策都需要考虑到未来的需求变化和可能的优化方向。