青训营X豆包MarsCode 技术训练营刷题技巧(动态规划专题篇二) | 豆包MarsCode AI 刷题

130 阅读3分钟

一、二分数字组合

题目描述

image.png

问题解析

本题是典型的 动态规划 + 子集和问题的变体,需要我们逐步分析数组的子集和可能性,以及如何将这些可能性映射到模运算范围内(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
  • 转移方程

    • 如果不选第 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] 分别表示可以构造模值为 AB 的分组方案数。

Step 4:结果判断
  1. 如果 dp[n][A] > 0dp[n][B] > 0,说明存在符合条件的分组。
  2. 返回 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)