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

62 阅读6分钟

问题描述

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

例如:如果有3张卡牌,正反面数字分别为 (1,2)(2,3) 和 (3,2),你需要找到所有满足这3张卡牌正面或背面朝上的数字之和可以被3整除的组合数。


测试样例

样例1:

输入:n = 3 ,a = [1, 2, 3] ,b = [2, 3, 2]
输出:3

样例2:

输入:n = 4 ,a = [3, 1, 2, 4] ,b = [1, 2, 3, 1]
输出:6

样例3:

输入:n = 5 ,a = [1, 2, 3, 4, 5] ,b = [1, 2, 3, 4, 5]
输出:32

问题理解

这个问题是一个典型的动态规划问题,涉及到对每张卡牌的两种选择(正面或背面)进行状态转移。我们需要计算所有可能的和模3的结果的数量,最终找到和为0的方案数。

数据结构选择

我们可以使用动态规划来解决这个问题。具体来说,我们可以使用一个数组 dp 来记录当前所有可能的和模3的结果的数量。动态规划的核心思想是将问题分解为子问题,并通过状态转移方程逐步求解。在这个问题中,我们使用一个长度为3的数组 dp 来记录当前所有可能的和模3的结果的数量。通过遍历每张卡牌,我们更新 dp 数组,最终得到所有卡牌正面或背面朝上的数字之和可以被3整除的组合数。

算法步骤

  1. 初始化:创建一个长度为3的数组 dp,初始值为 [1, 0, 0],表示和为0的方案数为1,和为1和2的方案数为0。
  2. 状态转移:对于每一张卡牌,我们更新 dp 数组。对于每张卡牌的正面和背面数字,我们分别计算它们对当前 dp 数组的影响。
  3. 结果:最终,dp[0] 就是所有卡牌正面或背面朝上的数字之和可以被3整除的组合数。

具体代码

public class Main {
    public static int solution(int n, int[] a, int[] b) {
        final int MOD = 1000000007;
        int[] dp = new int[3];
        dp[0] = 1; // 初始状态,和为0的方案数为1

        for (int i = 0; i < n; i++) {
            int[] newDp = new int[3];
            // 计算当前卡牌正面和背面的影响
            int aMod = a[i] % 3;
            int bMod = b[i] % 3;

            // 更新newDp数组
            for (int j = 0; j < 3; j++) {
                newDp[(j + aMod) % 3] = (newDp[(j + aMod) % 3] + dp[j]) % MOD;
                newDp[(j + bMod) % 3] = (newDp[(j + bMod) % 3] + dp[j]) % MOD;
            }

            // 更新dp数组
            dp = newDp;
        }

        return dp[0]; // 返回和为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[i]表示当前所有卡牌正面或背面数字之和模3等于i的方案数。由于我们关心的是能否被3整除,所以只需要考虑i为0、1、2的情况。

• 初始值:由于初始状态下没有任何卡牌被选中,所以和为0的方案数为1(即什么都不选),因此dp[0] = 1。而dp[1]和dp[2]则初始化为0,表示初始状态下不存在其他和模3等于1或2的方案。

2. 状态转移

• 遍历卡牌:对于每张卡牌,我们有两个选择:选正面或选背面。

• 计算影响:选择卡牌的正面或背面后,我们需要计算其对当前dp数组的影响。具体来说,如果当前卡牌的正面数字为a,背面数字为b,则选择正面后,我们需要将dp[i]的值加到newDp[(i + a) % 3]上;选择背面后,我们需要将dp[i]的值加到newDp[(i + b) % 3]上。

• 新数组:遍历完所有卡牌后,我们使用newDp数组的值更新原始的dp数组,为下一轮迭代做准备。

3. 结果输出

• 最终,dp[0]的值就是所有卡牌正面或背面朝上的数字之和可以被3整除的组合数。

学习建议

1. 理解动态规划基础

• 状态定义:明确每个状态的含义,以及状态之间的转移关系。

• 状态转移方程:理解并推导状态转移方程,这是动态规划的核心。

• 边界条件:注意初始状态的设定,以及特殊情况的处理。

2. 制定刷题计划

• 分阶段刷题:从简单到复杂,逐步提升难度。可以先从基础的动态规划问题开始,例如斐波那契数列、背包问题等。

• 定期复习:定期回顾已经解决的问题,加深理解。可以使用AI刷题功能中的错题本功能,记录并复习错题。

• 模拟考试:定期进行模拟考试,检验自己的学习效果。AI刷题功能可以提供模拟考试环境,帮助你熟悉考试节奏。

3. 利用错题进行针对性学习

• 分析错题:对于每道错题,分析错误原因,是理解问题不透彻,还是代码实现有误。

• 总结规律:总结常见错误类型和解决方法,形成自己的错题本。

• 针对性练习:针对薄弱环节进行针对性练习,例如动态规划中的状态转移方程理解不透彻,可以多做一些相关题目。

工具运用

1. 结合AI刷题功能

• 智能推荐:利用AI刷题功能的智能推荐功能,根据你的学习进度和错题情况,推荐适合的题目。

• 实时反馈:在刷题过程中,利用AI的实时反馈功能,及时发现并纠正错误。

• 错题本:使用错题本功能,记录并复习错题,形成自己的错题库。

2. 结合其他学习资源

• 在线课程:结合在线课程学习,例如Coursera、edX、LeetCode等平台上的算法课程,系统学习算法和数据结构。

• 书籍阅读:阅读经典算法书籍,例如《算法导论》、《深入理解计算机系统》等,深入理解算法原理和系统底层知识。

• 社区交流:参与算法学习社区,例如CSDN、GitHub、LeetCode讨论区、Stack Overflow等,与其他学习者交流经验,解决问题。同时,可以关注一些算法博客或公众号,获取最新的算法学习资料和行业动态。