题目解析与代码实现
问题背景
小M有 n 张卡牌,每张卡牌的正反面分别写着不同的数字。小M希望通过选择每张卡牌的一面,使得所有卡牌向上的数字之和可以被3整除。我们需要计算满足这个条件的不同方案数,并将结果对 ( + 7) 取模。
题目分析
这个问题可以通过动态规划(Dynamic Programming, DP)来解决。我们定义一个二维DP数组 dp[i][j],表示前 i 张卡牌的组合使得和的余数为 j 的方案数。其中 j 的取值范围是 0, 1, 2(因为数字之和被3整除的余数只可能是0, 1, 2)。
状态转移方程
对于第 i 张卡牌,我们有两种选择:选择正面 a[i-1] 或选择背面 b[i-1]。我们需要考虑每种选择对当前和的余数的影响:
-
选择正面
a[i-1]:- 新的余数为
(j - a[i-1] % 3 + 3) % 3。 - 因此,
dp[i][j] += dp[i-1][(j - a[i-1] % 3 + 3) % 3]。
- 新的余数为
-
选择背面
b[i-1]:- 新的余数为
(j - b[i-1] % 3 + 3) % 3。 - 因此,
dp[i][j] += dp[i-1][(j - b[i-1] % 3 + 3) % 3]。
- 新的余数为
初始条件
显然,当没有选择任何卡牌时,和为0的方案数为1,即 dp[0][0] = 1。
最终结果
最终我们需要的结果是 dp[n][0],即前 n 张卡牌的组合使得和的余数为0的方案数。
代码实现
private static final int MOD = 1000000007;
public static int solution(int n, int[] a, int[] b) {
// 初始化 dp 数组
long[][] dp = new long[n + 1][3];
dp[0][0] = 1; // 没有选择任何卡牌时,和为0的方案有1种
for (int i = 1; i <= n; i++) {
int ai = a[i - 1], bi = b[i - 1];
for (int j = 0; j < 3; j++) {
// 选择正面
dp[i][j] = (dp[i][j] + dp[i - 1][(j - ai % 3 + 3) % 3]) % MOD;
// 选择背面
dp[i][j] = (dp[i][j] + dp[i - 1][(j - bi % 3 + 3) % 3]) % MOD;
}
}
// 返回能被3整除的方案数量
return (int) dp[n][0];
}
详细思路解析
-
初始化:
dp[0][0] = 1:表示没有任何卡牌时,和为0的方案数为1。dp数组的维度是[n+1][3],其中n是卡牌的数量,3表示余数的范围(0, 1, 2)。
-
状态转移:
- 对于每一张卡牌
i,我们考虑两种选择:选择正面(a[i-1])或选择背面(b[i-1])。 - 更新
dp[i][j]时,需要从dp[i-1][*]中转移过来,具体来说:- 选择正面时,新的余数为
(j - a[i-1] % 3 + 3) % 3,因此从dp[i-1][(j - a[i-1] % 3 + 3) % 3]转移过来。 - 选择背面时,新的余数为
(j - b[i-1] % 3 + 3) % 3,因此从dp[i-1][(j - b[i-1] % 3 + 3) % 3]转移过来。
- 选择正面时,新的余数为
- 对于每一张卡牌
-
结果输出:
- 最终结果是
dp[n][0],表示前n张卡牌的组合中,和的余数为0的方案数。
- 最终结果是
复杂度分析
- 时间复杂度:
O(n * 3),即O(n),因为每个状态转移需要常数时间。 - 空间复杂度:
O(n * 3),即O(n),用于存储DP数组。