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

64 阅读2分钟

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

连续子串和的整除问题 - MarsCode

这题很巧妙地使用了前缀和与哈希表的性质,还要利用一点数学知识:a%b=c%b有(a-c)%b=0

摘要

本文介绍了如何在一个整数序列中找到所有和能被给定整数 bb 整除的连续子序列。使用前缀和技巧和哈希表来高效解决这个问题。算法的核心在于使用前缀和的余数来快速判断子序列的和是否能被 bb 整除,时间复杂度为 O(n)O(n)。代码实现包括Python和Go的版本。

问题描述

给定一个长度为 nn 的正整数序列 aa,以及一个正整数 bb,目标是找到所有和能被 bb 整除的连续子序列的数量。

示例

  • 输入:n = 3, b = 3, sequence = [1, 2, 3]
    输出:3

  • 输入:n = 4, b = 5, sequence = [5, 10, 15, 20]
    输出:10

  • 输入:n = 5, b = 2, sequence = [1, 2, 3, 4, 5]
    输出:6

原理分析

1. 前缀和

在连续子序列和问题中,前缀和是一种常用技巧。前缀和数组 prefixSum[i] 表示从数组开头到第 i 个元素的累加和。 对于任意子序列 sequence[i...j],它的和可以表示为:

sequence[i...j]=prefixSum[j+1]prefixSum[i]\text{sequence}[i...j] = \text{prefixSum}[j + 1] - \text{prefixSum}[i]

如果 prefixSum[j + 1] - prefixSum[i] 能被 bb 整除,那么我们找到一个符合条件的子序列。

2. 前缀和的余数

为了更高效地判断子数组和是否能被 bb 整除,可以使用前缀和的余数。 如果对于两个前缀和 prefixSum[i]prefixSum[j],它们对 bb 的余数相同,那么 prefixSum[j] - prefixSum[i] 一定能被 bb 整除。

证明:

为了证明算法的正确性,我们需要证明在 amodc=bmodca \mod c = b \mod c 的情况下,(ab)modc=0(a - b) \mod c = 0

  1. 假设条件:已知 amodc=bmodca \mod c = b \mod c

    根据模运算的定义,存在整数 kkmm,使得:

    a=kc+ra = k \cdot c + r
    b=mc+rb = m \cdot c + r

    其中 rraabbcc 取模的余数,因为 amodc=bmodca \mod c = b \mod c,所以两者的余数 rr 是相同的。

  2. 构造 aba - b

    计算 aba - b

    ab=(kc+r)(mc+r)a - b = (k \cdot c + r) - (m \cdot c + r)

    展开后得到:

    ab=kcmca - b = k \cdot c - m \cdot c

    cc 提出来:

    ab=(km)ca - b = (k - m) \cdot c

    因此,aba - bcc 的整数倍。

  3. 取模运算

    根据模运算的性质,如果 aba - bcc 的整数倍,那么:

    (ab)modc=0(a - b) \mod c = 0

因此,可以使用一个哈希表来记录前缀和的余数出现的次数,便于快速统计符合条件的子序列数量。

3. 算法步骤

  1. 初始化一个哈希表 remainderCount,用于记录前缀和余数出现的次数。
  2. 设置一个初始前缀和 currentSum = 0,并在哈希表中记录 remainderCount[0] = 1(表示从起点到当前位置的前缀和余数为 0 的情况)。
  3. 遍历数组,依次更新 currentSum,计算 currentSum % b,在哈希表中查询是否存在相同余数的前缀和。
  4. 将符合条件的子序列数累加到结果中。

代码实现

Python代码

from typing import List

def solution(n: int, b: int, sequence: List[int]) -> int:
    """
    该函数接收整数 n、一个除数 b 和一个整数列表 sequence。
    使用前缀和和哈希表来高效计算满足子数组和能被 b 整除的子数组数量。
    """
    # 哈希表记录前缀和的余数出现次数
    remainder_count = {0: 1}  # 初始化,表示前缀和为 0 的情况出现一次

    current_sum = 0  # 当前前缀和
    count = 0        # 记录符合条件的子序列数目

    # 遍历序列
    for num in sequence:
        current_sum += num
        remainder = current_sum % b

        # 检查当前余数是否已在哈希表中出现过
        if remainder in remainder_count:
            count += remainder_count[remainder]

        # 更新余数的出现次数
        remainder_count[remainder] = remainder_count.get(remainder, 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

Go语言代码

package main

import "fmt"

func solution(n int, b int, sequence []int) int {
	// 哈希表记录前缀和的余数出现次数
	remainderCount := make(map[int]int)
	remainderCount[0] = 1 // 初始条件,表示前缀和为 0 的情况

	currentSum := 0 // 当前前缀和
	count := 0      // 记录符合条件的子序列数目

	// 遍历序列
	for _, num := range sequence {
		currentSum += num
		remainder := currentSum % b
		// 检查当前余数是否已在哈希表中出现过
		if freq, exists := remainderCount[remainder]; exists {
			count += freq
		}

		// 更新余数的出现次数
		remainderCount[remainder]++
	}

	return count
}

func main() {
	// 测试用例
	fmt.Println(solution(3, 3, []int{1, 2, 3}))           // 输出:3
	fmt.Println(solution(4, 5, []int{5, 10, 15, 20}))      // 输出:10
	fmt.Println(solution(5, 2, []int{1, 2, 3, 4, 5}))      // 输出:6
}