问题描述
小M有 nn 张卡牌,每张卡牌的正反面分别写着不同的数字,正面是 aiai,背面是 bibi。小M希望通过选择每张卡牌的一面,使得所有向上的数字之和可以被3整除。你需要告诉小M,一共有多少种不同的方案可以满足这个条件。由于可能的方案数量过大,结果需要对 109+7109+7 取模。
例如:如果有3张卡牌,正反面数字分别为 (1,2),(2,3) 和 (3,2),你需要找到所有满足这3张卡牌正面或背面朝上的数字之和可以被3整除的组合数。
算法选择
dfs + 记忆化搜索
算法思路
-
初始化:
- 创建一个二维数组
memo,用于存储动态规划的结果,其中memo[i][j]表示在前i张卡牌中选择数字,使得这些数字之和模3等于j的方案数。 - 将
memo数组中的所有元素初始化为-1,表示尚未计算。
- 创建一个二维数组
-
深度优先搜索(DFS) :
- 定义一个递归函数
dfs(i, j),表示在前i张卡牌中选择数字,使得这些数字之和模3等于j的方案数。 - 如果
i < 0,即没有卡牌可选,那么只有当j == 0时,方案数为1(表示和为0),否则方案数为0。 - 如果
memo[i][j]不等于-1,直接返回memo[i][j]的值,表示已经计算过。 - 否则,计算
dfs(i - 1, ((j + a[i]) % 3))和dfs(i - 1, ((j + b[i]) % 3)),分别表示选择第i张卡牌的正面和背面的方案数。 - 将这两个方案数相加,并对结果取模(
MOD = 1e9 + 7),然后存储在memo[i][j]中。
- 定义一个递归函数
-
返回结果:
- 最终,
dfs(n - 1, 0)的值就是所有方案中,数字之和可以被3整除的方案数。
- 最终,
代码展示
public class Main {
private static int[][] memo;
private static final int MOD = (int) (1e9 + 7);
public static int solution(int n, int[] a, int[] b) {
memo = new int[n][3];
for (int i = 0; i < n; i++) {
Arrays.fill(memo[i], -1);
}
return dfs(n - 1, 0, a, b);
}
// dfs(i, j): 表示从(0-i)范围内选取元素,
// j: 已选数字之和 % 3 == j
// dfs(-1, 0) = 1, dfs(-1, 1) = 0 dfs(-1, 2) = 0
// dfs(i, j) = dfs(i-1, (j+a)%3) + dfs(i-1, j)
public static int dfs(int i, int j, int[] a, int[] b) {
if (i < 0) {
if (j == 0) {
return 1;
} else {
return 0;
}
}
if (memo[i][j] != -1) {
return memo[i][j];
}
long res = 0;
res = dfs(i - 1, ((j + a[i]) % 3), a, b) + dfs(i - 1, ((j + b[i]) % 3), a, b);
return memo[i][j] = (int) (res % MOD);
}
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);
}
}