33. 卡牌翻面求和问题 | 豆包MarsCode AI刷题

67 阅读3分钟

题目解析与代码实现

问题背景

小M有 n 张卡牌,每张卡牌的正反面分别写着不同的数字。小M希望通过选择每张卡牌的一面,使得所有卡牌向上的数字之和可以被3整除。我们需要计算满足这个条件的不同方案数,并将结果对 (10910^9 + 7) 取模。

题目分析

这个问题可以通过动态规划(Dynamic Programming, DP)来解决。我们定义一个二维DP数组 dp[i][j],表示前 i 张卡牌的组合使得和的余数为 j 的方案数。其中 j 的取值范围是 0, 1, 2(因为数字之和被3整除的余数只可能是0, 1, 2)。

状态转移方程

对于第 i 张卡牌,我们有两种选择:选择正面 a[i-1] 或选择背面 b[i-1]。我们需要考虑每种选择对当前和的余数的影响:

  1. 选择正面 a[i-1]

    • 新的余数为 (j - a[i-1] % 3 + 3) % 3
    • 因此,dp[i][j] += dp[i-1][(j - a[i-1] % 3 + 3) % 3]
  2. 选择背面 b[i-1]

    • 新的余数为 (j - b[i-1] % 3 + 3) % 3
    • 因此,dp[i][j] += dp[i-1][(j - b[i-1] % 3 + 3) % 3]

初始条件

显然,当没有选择任何卡牌时,和为0的方案数为1,即 dp[0][0] = 1

最终结果

最终我们需要的结果是 dp[n][0],即前 n 张卡牌的组合使得和的余数为0的方案数。

代码实现

private static final int MOD = 1000000007;

public static int solution(int n, int[] a, int[] b) {
    // 初始化 dp 数组
    long[][] dp = new long[n + 1][3];
    dp[0][0] = 1; // 没有选择任何卡牌时,和为0的方案有1种

    for (int i = 1; i <= n; i++) {
        int ai = a[i - 1], bi = b[i - 1];
        for (int j = 0; j < 3; j++) {
            // 选择正面
            dp[i][j] = (dp[i][j] + dp[i - 1][(j - ai % 3 + 3) % 3]) % MOD;
            // 选择背面
            dp[i][j] = (dp[i][j] + dp[i - 1][(j - bi % 3 + 3) % 3]) % MOD;
        }
    }

    // 返回能被3整除的方案数量
    return (int) dp[n][0];
}

详细思路解析

  1. 初始化

    • dp[0][0] = 1:表示没有任何卡牌时,和为0的方案数为1。
    • dp数组的维度是 [n+1][3],其中 n 是卡牌的数量,3 表示余数的范围(0, 1, 2)。
  2. 状态转移

    • 对于每一张卡牌 i,我们考虑两种选择:选择正面(a[i-1])或选择背面(b[i-1])。
    • 更新 dp[i][j] 时,需要从 dp[i-1][*] 中转移过来,具体来说:
      • 选择正面时,新的余数为 (j - a[i-1] % 3 + 3) % 3,因此从 dp[i-1][(j - a[i-1] % 3 + 3) % 3] 转移过来。
      • 选择背面时,新的余数为 (j - b[i-1] % 3 + 3) % 3,因此从 dp[i-1][(j - b[i-1] % 3 + 3) % 3] 转移过来。
  3. 结果输出

    • 最终结果是 dp[n][0],表示前 n 张卡牌的组合中,和的余数为0的方案数。

复杂度分析

  • 时间复杂度O(n * 3),即 O(n),因为每个状态转移需要常数时间。
  • 空间复杂度O(n * 3),即 O(n),用于存储DP数组。