一、二分数字组合
题目描述
问题解析
本题是典型的 动态规划 + 子集和问题的变体,需要我们逐步分析数组的子集和可能性,以及如何将这些可能性映射到模运算范围内(0 到 9)。
本题是一道经典的 模运算动态规划 问题,字节AI刷题难度分为易,但可以通过这道题推广到其他类似变体问题:
- 子集和问题(判断是否存在和为某值的子集)。
- 分组问题(分成两组满足某种条件)。
- 取模问题(模运算约束下的最优方案)。
以下是解题步骤:
Step 1:前置条件优化
-
对数组的每个元素取模 10: 由于题目只关注分组和对 10 取模的结果,可以直接将
array_a的每个元素对 10 取模,减少数据规模和运算复杂度。array_a = [x % 10 for x in array_a] -
快速验证总和模是否可行: 如果整个数组的和
total_sum % 10不满足(A + B) % 10,则分组不可能成立,直接返回 0。
Step 2:动态规划定义
定义一个二维数组 dp[i][j]:
dp[i][j]表示从前i个元素中,能取出若干个数,使它们的和对 10 取模等于j的方案数。- 目标是计算所有可能的子集和模值,判断最终是否有分组满足条件。
Step 3:动态规划状态转移
-
初始状态:
- 空集的和为 0,因此
dp[0][0] = 1,其他dp[0][j] = 0。
- 空集的和为 0,因此
-
转移方程:
- 如果不选第
i个数,状态不变:
dp[i][j] += dp[i-1][j]- 如果选第
i个数,模值更新为(j - array_a[i-1] + 10) % 10:
dp[i][j] += dp[i-1][(j - array_a[i-1] + 10) % 10] - 如果不选第
-
目标: 动态规划完成后,
dp[n][A]和dp[n][B]分别表示可以构造模值为A和B的分组方案数。
Step 4:结果判断
- 如果
dp[n][A] > 0且dp[n][B] > 0,说明存在符合条件的分组。 - 返回
dp[n][B],即满足条件的具体方案数。
Python代码
def solution(n, A, B, array_a):
# 将数组元素取模 10,简化计算
array_a = [x % 10 for x in array_a]
# 计算数组的总和模 10
total_sum = sum(array_a)
total_sum %= 10
# 提前判断是否有解
if total_sum == A or total_sum == B:
return 1
if total_sum != (A + B) % 10:
return 0
# 定义动态规划数组 dp[i][j]
# dp[i][j] 表示前 i 个数字中,子集和模 10 等于 j 的方案数
dp = [[0 for _ in range(10)] for _ in range(n + 1)]
dp[0][0] = 1 # 初始化:空集和为 0
# 状态转移
for i in range(1, n + 1):
for j in range(10):
# 不选当前数字
dp[i][j] += dp[i - 1][j]
# 选当前数字
dp[i][j] += dp[i - 1][(j - array_a[i - 1] + 10) % 10]
# 返回结果
return dp[n][B]
if __name__ == "__main__":
# 测试用例
print(solution(3, 1, 2, [1, 1, 1]) == 3)
print(solution(3, 3, 5, [1, 1, 1]) == 1)
print(solution(2, 1, 1, [1, 1]) == 2)
print(solution(13, 8, 3, [21, 9, 16, 7, 9, 19, 8, 4, 1, 17, 1, 10, 16]) == 1)