做题笔记:卡片翻面求和问题
题目理解
题目要求我们通过选择每张卡牌的一面,使得所有向上的数字之和可以被3整除。这是一个典型的动态规划问题,需要我们通过逐步构建状态来找到所有可能的组合数。
数据结构选择
在这个问题中,我们选择使用一个二维数组 dp[i][j] 来表示前 i 张卡牌中,选择某些面朝上使得数字之和模3余 j 的方案数。这里的 i 表示前 i 张卡牌,j 表示数字之和模3的结果。
算法步骤
-
初始化:
dp[0][0] = 1,表示前0张卡牌(即没有卡牌)时,数字之和为0的方案数为1。dp[0][1] = 0和dp[0][2] = 0,表示前0张卡牌时,数字之和模3余1或2的方案数为0。
-
状态转移:
- 对于每一张卡牌
i,我们有两个选择:选择正面a[i-1]或选择背面b[i-1]。 - 对于每一个可能的余数
j,我们更新dp[i][(j + a[i-1]) % 3]和dp[i][(j + b[i-1]) % 3]。
- 对于每一张卡牌
-
结果返回:
- 最终的结果是
dp[n][0],即前n张卡牌中,数字之和模3余0的方案数。
- 最终的结果是
代码详解
public class Main {
public static int solution(int n, int[] a, int[] b) {
final int MOD = 1000000007;
int[][] dp = new int[n + 1][3];
// 初始化
dp[0][0] = 1;
// 状态转移
for (int i = 1; i <= n; i++) {
for (int j = 0; j < 3; j++) {
// 选择正面的情况
int newSum = (j + a[i - 1]) % 3;
dp[i][newSum] = (dp[i][newSum] + dp[i - 1][j]) % MOD;
// 选择背面的情况
newSum = (j + b[i - 1]) % 3;
dp[i][newSum] = (dp[i][newSum] + 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(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);
}
}
#### 总结知识点
-
动态规划:
- 动态规划是一种通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。
- 在这个问题中,我们通过构建一个二维数组
dp来存储中间结果,避免了重复计算。
-
模运算:
- 模运算在处理循环计数和周期性问题时非常有用。
- 在这个问题中,我们通过模3运算来简化状态的表示和转移。
-
状态转移方程:
- 状态转移方程是动态规划的核心,它描述了如何从一个状态转移到另一个状态。
- 在这个问题中,状态转移方程为
dp[i][(j + a[i-1]) % 3]和dp[i][(j + b[i-1]) % 3]。
个人思考与分析
动态规划问题的关键在于找到合适的状态表示和状态转移方程。在这个问题中,我们通过模3运算将问题简化,使得状态转移更加直观。此外,初始状态的设定也非常重要,它决定了整个动态规划过程的起点。