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

60 阅读4分钟

题目解析

小M手中有 nnn 张卡牌,每张卡牌的正反面分别写有数字 a[i]a[i]a[i] 和 b[i]b[i]b[i]。她希望选择每张卡牌的一面,使得向上的数字之和可以被 333 整除。我们需要计算满足条件的不同组合数,并对结果取模 109+710^9 + 7109+7。

这一问题本质上是一个 动态规划问题,核心在于如何高效计算所有可能的数字和模 333 的情况,并在此过程中进行状态转移。


解题思路

1. 状态定义

使用动态规划数组 dp[i][j] 表示:

  • 前 iii 张卡牌考虑完后,所有向上数字之和对 333 取模等于 jjj 的方案数。
  • 其中 j∈{0,1,2}j \in {0, 1, 2}j∈{0,1,2}。

我们最终关心的是 dp[n][0],即 nnn 张卡牌选完后,向上数字之和能被 333 整除的方案数。

2. 初始状态

在没有选任何卡牌时:

  • dp[0][0]=1dp[0][0] = 1dp[0][0]=1:数字之和为 000 且满足整除条件的方案数为 1(空集)。
  • dp[0][1]=dp[0][2]=0dp[0][1] = dp[0][2] = 0dp[0][1]=dp[0][2]=0:没有选卡牌时,数字之和模 333 为 111 或 222 的方案数为 0。
3. 状态转移

对于第 iii 张卡牌:

  • 如果选择正面数字 a[i]a[i]a[i],当前的模值会发生变化:(j+a[i])%3(j + a[i]) % 3(j+a[i])%3。
  • 如果选择反面数字 b[i]b[i]b[i],当前的模值会发生变化:(j+b[i])%3(j + b[i]) % 3(j+b[i])%3。

因此,状态转移方程为:

dp[i][(j+a[i])%3]+=dp[i−1][j]dp[i][(j + a[i]) % 3] += dp[i-1][j]dp[i][(j+a[i])%3]+=dp[i−1][j] dp[i][(j+b[i])%3]+=dp[i−1][j]dp[i][(j + b[i]) % 3] += dp[i-1][j]dp[i][(j+b[i])%3]+=dp[i−1][j]

需要注意的是,每次更新后对结果取模 109+710^9 + 7109+7。

4. 结果输出

所有卡牌选完后,满足整除条件的方案数为 dp[n][0]


优化分析

空间优化

  • 注意到在状态转移时,当前状态仅依赖上一状态,因此可以用一维数组 dp 滚动更新,减少空间复杂度。

实现代码

以下是完整的 Java 实现:

java
Copy code
public class Main {
    public static int solution(int n, int[] a, int[] b) {
        int MOD = 1000000007;
        // 定义 dp 数组,初始化为 0
        int[] dp = new int[3];
        dp[0] = 1; // 初始状态

        for (int i = 0; i < n; i++) {
            int[] next = new int[3]; // 临时数组存储当前状态
            for (int j = 0; j < 3; j++) {
                next[(j + a[i]) % 3] = (next[(j + a[i]) % 3] + dp[j]) % MOD;
                next[(j + b[i]) % 3] = (next[(j + b[i]) % 3] + dp[j]) % MOD;
            }
            dp = next; // 更新 dp 数组
        }

        return dp[0]; // 最终答案
    }

    public static void main(String[] args) {
        System.out.println(solution(3, new int[]{1, 2, 3}, new int[]{2, 3, 2}) == 3);
        System.out.println(solution(4, new int[]{3, 1, 2, 4}, new int[]{1, 2, 3, 1}) == 6);
        System.out.println(solution(5, new int[]{1, 2, 3, 4, 5}, new int[]{1, 2, 3, 4, 5}) == 32);
    }
}

示例解释

  1. 输入:
    n=3,a=[1,2,3],b=[2,3,2]n = 3, a = [1, 2, 3], b = [2, 3, 2]n=3,a=[1,2,3],b=[2,3,2]
    输出:3
    说明:三种满足条件的组合是:

    • a[1],a[2],b[3]a[1], a[2], b[3]a[1],a[2],b[3]:1 + 2 + 2 = 5 %3=0% 3 = 0%3=0。
    • b[1],a[2],a[3]b[1], a[2], a[3]b[1],a[2],a[3]:2 + 2 + 3 = 7 %3=0% 3 = 0%3=0。
    • a[1],b[2],b[3]a[1], b[2], b[3]a[1],b[2],b[3]:1 + 3 + 2 = 6 %3=0% 3 = 0%3=0。
  2. 输入:
    n=4,a=[3,1,2,4],b=[1,2,3,1]n = 4, a = [3, 1, 2, 4], b = [1, 2, 3, 1]n=4,a=[3,1,2,4],b=[1,2,3,1]
    输出:6


时间与空间复杂度

  • 时间复杂度:O(n×3)=O(n)O(n \times 3) = O(n)O(n×3)=O(n),每张卡牌最多更新 333 种模值。
  • 空间复杂度:O(3)=O(1)O(3) = O(1)O(3)=O(1),使用滚动数组优化空间。

总结

本题通过动态规划解决了一个组合数统计问题。核心在于理解模 333 的状态转移,并通过空间优化实现高效计算。这种动态规划思路可扩展到更多模运算相关的场景。