题目分析
题目给定一组卡牌,每张卡牌有两个数字:正面和背面。我们的目标是选择每张卡牌的正面或背面,使得所有选择的数字的和可以被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] = 0 和 dp[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);
}
}
代码解析
-
初始化
dp数组:- 初始时,
dp[0] = 1,表示和为0的方案数为1(即没有选择任何卡牌时,和为0)。 dp[1] = 0和dp[2] = 0,表示没有选择卡牌时和为1或2的方案数为0。
- 初始时,
-
状态转移:
- 对于每张卡牌,遍历当前所有可能的和对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的方案数,即满足题目要求的方案数。
- 循环结束后,
-
时间复杂度:
- 每张卡牌需要遍历
dp数组(大小为3),所以时间复杂度是 O(n),其中n是卡牌的数量。
- 每张卡牌需要遍历
-
空间复杂度:
- 使用了一个大小为3的数组
dp,因此空间复杂度是 O(1)。
- 使用了一个大小为3的数组
总结
该算法通过动态规划巧妙地解决了这个问题,利用了对3取模的状态转移,避免了暴力枚举所有组合的高时间复杂度,达到了线性时间复杂度 O(n),非常高效。