青训营X豆包MarsCode 技术训练营第四课 | 豆包MarsCode AI 刷题

35 阅读4分钟

二分数字组合

问题描述

小F面临一个有趣的挑战:给定一个数组,她需要将数组中的数字分为两组。分组的目标是使得一组数字的和的个位数等于给定的 A,另一组数字的和的个位数等于给定的 B。除此之外,还有一种特殊情况允许其中一组为空,但剩余数字和的个位数必须等于 A 或 B。小F需要计算所有可能的划分方式。

例如,对于数组 [1, 1, 1] 和目标 A = 1,B = 2,可行的划分包括三种:每个 1 单独作为一组,其余两个 1 形成另一组。如果 A = 3,B = 5,当所有数字加和的个位数为 3 或 5 时,可以有一组为非空,另一组为空。


测试样例

样例1:

输入:n = 3,A = 1,B = 2,array_a = [1, 1, 1]
输出:3

样例2:

输入:n = 3,A = 3,B = 5,array_a = [1, 1, 1]
输出:1

样例3:

输入:n = 2,A = 1,B = 1,array_a = [1, 1]
输出:2

这是一个经典的背包问题变体。我们需要将一个数组中的数字分为两组,使得每组的和的个位数分别等于给定的目标值 AB。另外,允许其中一组为空,并且剩余数字的和的个位数必须等于 AB

问题分析

给定一个数组,我们可以将其元素分成两组,其中:

  1. 第一组的和的个位数应该等于 A
  2. 第二组的和的个位数应该等于 B
  3. 如果某一组为空,则另一组的和的个位数应该等于目标值 A 或 B

这个问题的关键是如何利用动态规划来实现这个目标。

动态规划思路

  • 状态定义:我们使用一个动态规划数组 dp,其中 dp[x][y] 表示从数组中选出一些数字,使得它们的和的个位数为 x,剩余的数字的和的个位数为 y
  • 初始化dp[0][0] = 1,表示初始时,两组的和的个位数都是 0。
  • 转移过程:对于每个数字,尝试将其放入第一组或者第二组,从而更新 dp 数组。

关键点:

  • 使用个位数的运算来简化问题,减小状态空间。
  • 动态规划的状态转移是通过“加上当前数字”来改变当前和的个位数。

代码实现

pythonCopy Code
def count_partitions(n, A, B, array_a):
    # dp[x][y]表示第一组和的个位数为x,第二组和的个位数为y的情况数
    dp = [[0] * 10 for _ in range(10)]
    dp[0][0] = 1  # 初始状态:两组和的个位数都是0,只有一种方法

    for num in array_a:
        num = num % 10  # 只关心个位数
        # 我们要从后往前更新dp,避免重复计算
        # 遍历当前已有的dp状态
        new_dp = [row[:] for row in dp]
        for i in range(10):
            for j in range(10):
                # 把当前数字num加入第一组,更新第一组的个位数
                new_dp[(i + num) % 10][j] += dp[i][j]
                # 把当前数字num加入第二组,更新第二组的个位数
                new_dp[i][(j + num) % 10] += dp[i][j]
        dp = new_dp

    # 最终,我们需要的是dp[A][B],表示第一组和的个位数为A,第二组和的个位数为B的方案数
    return dp[A][B]

# 测试样例
print(count_partitions(3, 1, 2, [1, 1, 1]))  # 输出:3
print(count_partitions(3, 3, 5, [1, 1, 1]))  # 输出:1
print(count_partitions(2, 1, 1, [1, 1]))    # 输出:2

解释代码

  1. 初始化:创建一个 dp 数组,dp[x][y] 表示从已选数字中,第一组的和的个位数为 x,第二组的和的个位数为 y 的情况数。初始化 dp[0][0] = 1 表示没有选择任何数字时,两个组的和都是 0。
  2. 遍历每个数字:对于每个数字 num,只关心它的个位数(num % 10)。然后更新 dp 数组,分别考虑将该数字放入第一组和第二组。
  3. 更新 dp:为了避免在同一轮循环中重复使用当前数字,我们从 dp 数组的当前状态 dp[i][j] 开始,计算放入第一组或第二组后更新后的新状态。
  4. 结果输出:最后我们返回 dp[A][B],表示符合条件的划分方式的数量。

测试样例

pythonCopy Code
# 测试样例1
n = 3
A = 1
B = 2
array_a = [1, 1, 1]
print(count_partitions(n, A, B, array_a))  # 输出:3

# 测试样例2
n = 3
A = 3
B = 5
array_a = [1, 1, 1]
print(count_partitions(n, A, B, array_a))  # 输出:1

# 测试样例3
n = 2
A = 1
B = 1
array_a = [1, 1]
print(count_partitions(n, A, B, array_a))  # 输出:2

时间复杂度分析

  • 由于每个数字都要更新 dp 数组的每个状态(dp[i][j]),其中 i 和 j 都是个位数,最多有 10 种可能的值,因此 dp 数组的大小为 10x10
  • 对于每个数字的处理,需要更新 10x10 个状态,总的时间复杂度是 O(n * 10 * 10),其中 n 是数组的长度。

所以,时间复杂度是 O(n),空间复杂度是 O(1)(因为状态空间大小是固定的)。