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

107 阅读4分钟

题目分析

题目给定一组卡牌,每张卡牌有两个数字:正面和背面。我们的目标是选择每张卡牌的正面或背面,使得所有选择的数字的和可以被3整除。

由于卡牌的数量 n 可能非常大,暴力求解所有的组合会导致计算量巨大,因此我们需要使用一种更高效的算法来解决问题。这个问题的核心是,如何在众多的选择中,找到符合特定条件(数字和能被3整除)的方案。我们可以通过动态规划(DP)来高效地解决这个问题。

核心思路

我们可以将问题转化为 模3 问题。具体来说,对于每张卡牌,有两个可能的数字选择(正面和背面)。我们需要计算所有选择后的和,看看它们是否能被3整除。

动态规划的核心思想

我们通过动态规划的方式,维护一个状态数组 dp,表示当前选择过程中,和对3取模的结果。假设 dp[i] 表示和对3取模为 i 的方案数量,其中 i 取值为 0, 1, 2

  • dp[0] 表示和对3取模为0的方案数量。
  • dp[1] 表示和对3取模为1的方案数量。
  • dp[2] 表示和对3取模为2的方案数量。

动态规划的状态转移

  • 每张卡牌的选择影响当前和的对3取模结果。如果当前的和对3取模为 j,选择卡牌的正面数字 a[i] 后,新的和对3取模为 (j + a[i]) % 3;同理,选择背面数字 b[i] 后,新的和对3取模为 (j + b[i]) % 3
  • 我们使用一个临时数组 new_dp 来存储转移后的新状态,最终再将 new_dp 更新回 dp

初始状态

初始时,dp[0] = 1,即没有选择卡牌时,和为0的方案数为1(这是一个特殊情况,表示初始状态下我们已经有一种和为0的方案)。而 dp[1] = 0dp[2] = 0,表示没有选择卡牌时,和为1或2的方案数为0。

最终结果

经过所有卡牌的选择后,dp[0] 就表示能够使总和对3取模为0的方案数,也就是符合题目要求的方案数。

代码实现

public class Main {
    private static final int MOD = 1000000007;
    public static int solution(int n, int[] a, int[] b) {
        // dp数组,存储和对3取模的情况
        long[] dp = new long[3];
        dp[0] = 1; // 初始状态

        // 遍历每张卡牌
        for (int i = 0; i < n; i++) {
            long[] new_dp = new long[3];

            // 计算选择正面和背面的情况
            for (int j = 0; j < 3; j++) {
                new_dp[(j + a[i]) % 3] = (new_dp[(j + a[i]) % 3] + dp[j]) % MOD; // 选择正面
                new_dp[(j + b[i]) % 3] = (new_dp[(j + b[i]) % 3] + dp[j]) % MOD; // 选择背面
            }

            // 更新dp为新状态
            dp = new_dp;
        }

        // 输出能被3整除的组合数
        return (int)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. 初始化 dp 数组

    • 初始时,dp[0] = 1,表示和为0的方案数为1(即没有选择任何卡牌时,和为0)。
    • dp[1] = 0 和 dp[2] = 0,表示没有选择卡牌时和为1或2的方案数为0。
  2. 状态转移

    • 对于每张卡牌,遍历当前所有可能的和对3取模的结果(dp[j]),更新 new_dp[(j + a[i]) % 3] 和 new_dp[(j + b[i]) % 3]
    • 这里的 dp[j] 表示当前和对3取模为 j 的方案数,选择正面 a[i] 后,新的和对3取模为 (j + a[i]) % 3,同样地,选择背面 b[i] 后,新的和对3取模为 (j + b[i]) % 3
  3. 结果输出

    • 循环结束后,dp[0] 存储的是所有方案中和对3取模为0的方案数,即满足题目要求的方案数。
  4. 时间复杂度

    • 每张卡牌需要遍历 dp 数组(大小为3),所以时间复杂度是 O(n),其中 n 是卡牌的数量。
  5. 空间复杂度

    • 使用了一个大小为3的数组 dp,因此空间复杂度是 O(1)。

总结

该算法通过动态规划巧妙地解决了这个问题,利用了对3取模的状态转移,避免了暴力枚举所有组合的高时间复杂度,达到了线性时间复杂度 O(n),非常高效。