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

51 阅读4分钟

问题描述

小M有 nn 张卡牌,每张卡牌的正反面分别写着不同的数字,正面是 aiai​,背面是 bibi​。小M希望通过选择每张卡牌的一面,使得所有向上的数字之和可以被3整除。你需要告诉小M,一共有多少种不同的方案可以满足这个条件。由于可能的方案数量过大,结果需要对 109+7109+7 取模。

思路分析

  1. 问题本质

    • 我们需要找到一种方法,使得 nn 张卡牌的正面或背面朝上的数字之和可以被3整除。
    • 每张卡牌有2种选择(正面或背面),因此总共有 2n2n 种不同的组合方式。
  2. 动态规划

    • 使用动态规划(Dynamic Programming, DP)来解决这个问题。定义一个二维数组 dp,其中 dp[i][j] 表示前 i 张卡牌的数字之和模3等于 j 的方案数。
    • 初始化 dp[0][0] = 1,表示没有卡牌时,数字之和模3为0的方案数为1(空集)。
    • 遍历每张卡牌,更新 dp 数组。
  3. 状态转移方程

    • 对于第 i 张卡牌,假设其正面数字为 front,背面数字为 back

    • 更新 dp 数组的公式为:

 dp[i][(j+front)%3]=dp[i][(j+front)%3]+dp[i-1][j]%MOD;
 dp[i][(j+back)%3]=dp[i][(j+back)%3]+dp[i-1][j]%MOD;

代码详解

public class Main {
    public static int solution(int n, int[] a, int[] b) {
        final int MOD = 100000007;
        int[][] dp = new int[n + 1][3];
        
        // 初始化dp数组
        dp[0][0] = 1;
        
        // 遍历每张卡牌
        for (int i = 1; i <= n; i++) {
            int front = a[i - 1];
            int back = b[i - 1];
            
            // 遍历前一张卡牌的三种状态
            for (int j = 0; j < 3; j++) {
                dp[i][(j + front) % 3] = (dp[i][(j + front) % 3] + dp[i - 1][j]) % MOD;
                dp[i][(j + back) % 3] = (dp[i][(j + back) % 3] + dp[i - 1][j]) % MOD;
            }
        }
        
        // 返回结果
        return dp[n][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(3, new int[]{1, 2, 3}, new int[]{2, 3, 2}) == 3); // 输出 true
        System.out.println(solution(4, new int[]{3, 1, 2, 4}, new int[]{1, 2, 3, 1}) == 6); // 输出 true
        System.out.println(solution(5, new int[]{1, 2, 3, 4, 5}, new int[]{1, 2, 3, 4, 5}) == 32); // 输出 true
    }
}

知识总结

  1. 动态规划

    • 动态规划是一种通过将问题分解成子问题来解决复杂问题的方法。在这个问题中,我们使用了一个二维数组 dp 来记录每张卡牌的处理状态。
    • 初始化 dp[0][0] = 1,表示没有卡牌时的初始状态。
    • 状态转移方程 dp[i][(j + front) % 3] 和 dp[i][(j + back) % 3] 用于更新每张卡牌的处理结果。
  2. 模运算

    • 由于结果可能非常大,我们需要对 109+7109+7 取模。取模运算可以防止整数溢出,并且保持计算结果在合理范围内。
    • 在更新 dp 数组时,每次累加结果后都需要取模。
  3. 数组处理

    • 使用 for 循环遍历数组中的每一个元素,处理每张卡牌。
    • 通过 a[i - 1] 和 b[i - 1] 分别获取每张卡牌的正面和背面数字。
  4. 边界条件

    • 考虑边界条件,确保数组索引不越界。例如,处理第 i 张卡牌时,数组索引为 i - 1

个人理解与学习建议

  1. 问题简化

    • 这个问题的核心是找到一种方法,使得所有卡牌的数字之和模3为0。通过动态规划,我们可以有效地解决这个问题。
    • 动态规划的状态转移方程是解决此类问题的关键,需要仔细理解和推导。
  2. 代码可读性

    • 在编写代码时,尽量使用有意义的变量名,如 front 和 back,这样可以使代码更易读。
    • 添加注释,特别是对于复杂逻辑的部分,可以提高代码的可维护性。
  3. 学习建议

    • 理解动态规划:动态规划是一种重要的算法思路,有助于解决很多复杂问题。通过这个题目,可以加深对动态规划的理解。
    • 掌握模运算:模运算是处理大数问题的有效方法,特别是在结果需要取模的情况下,熟练掌握模运算可以避免很多错误。
    • 编写测试用例:在编写代码时,先思考测试用例,确保代码的正确性和鲁棒性。添加一些边界情况的测试用例,如卡牌数量为1、数组中包含0等。
    • 代码优化:在确保代码正确性的基础上,可以考虑优化代码的性能,例如使用并行流处理大数组。