问题描述
小M有 n 张卡牌,每张卡牌的正反面分别写着不同的数字。正面数字为 a[i],背面数字为 b[i]。小M希望通过选择每张卡牌的一面,使得所有卡牌正面或背面朝上的数字之和可以被 3 整除。你需要告诉小M,一共有多少种不同的方案可以满足这个条件。由于可能的方案数量过大,结果需要对 109+7109+7 取模。
解题思路
-
状态转移
这道题是一个典型的动态规划问题,我们可以通过状态转移来解决。设dp[i][j]表示前i张卡牌选择后,数字和对 3 取余结果为j的方案数。目标是计算出当所有卡牌选择完后,数字和对 3 取余结果为0的方案数。 -
状态初始化
初始时没有卡牌时,数字和为0,所以dp[0][0] = 1,其他状态为 0。 -
状态转移
对于每张卡牌,我们有两种选择:选择卡牌的正面或选择背面。通过对这两种选择的数字和对 3 取余结果进行更新,来更新状态。- 对于卡牌的正面数字
a[i],我们可以通过dp[i-1][(j - a[i] % 3) % 3]转移。 - 对于卡牌的背面数字
b[i],我们可以通过dp[i-1][(j - b[i] % 3) % 3]转移。
- 对于卡牌的正面数字
-
取模操作
由于可能的方案数过大,我们每次计算时都要对 109+7109+7 取模。 -
最终结果
我们需要返回的是dp[n][0],即所有卡牌选择完成后,数字和对 3 取余为 0 的方案数。
代码实现
def solution(n: int, a: list, b: list) -> int:
MOD = 10**9 + 7
dp = [[0] * 3 for _ in range(n + 1)]
dp[0][0] = 1
for i in range(1, n + 1):
a_mod = a[i - 1] % 3
b_mod = b[i - 1] % 3
for j in range(3):
dp[i][j] = (dp[i - 1][(j - a_mod) % 3] + dp[i - 1][(j - b_mod) % 3]) % MOD
return dp[n][0]
复杂度分析
-
时间复杂度:
- 初始化
dp数组需要 O(n) 的时间。 - 动态规划状态转移时,对于每张卡牌和每种取余情况,我们都需要进行状态转移,因此时间复杂度为 O(n * 3)。
综合时间复杂度为 O(n),由于每次状态转移只涉及常数次计算。
- 初始化
-
空间复杂度:
dp数组的大小为(n + 1) * 3,因此空间复杂度为 O(n)。
测试用例
用例 1
输入:
n = 3, a = [1, 2, 3], b = [2, 3, 2]
输出:
3
解释:有 3 张卡牌,正反面数字分别为 (1, 2), (2, 3), (3, 2)。所有满足和对 3 取余为 0 的方案有 3 种。
用例 2
输入:
复制代码
n = 4, a = [3, 1, 2, 4], b = [1, 2, 3, 1]
输出:
6
解释:有 4 张卡牌,正反面数字分别为 (3, 1), (1, 2), (2, 3), (4, 1)。总共有 6 种方案可以使得数字和对 3 取余为 0。
用例 3
输入:
n = 5, a = [1, 2, 3, 4, 5], b = [1, 2, 3, 4, 5]
输出:
32
解释:有 5 张卡牌,正反面数字分别为 (1, 1), (2, 2), (3, 3), (4, 4), (5, 5)。总共有 32 种方案可以使得数字和对 3 取余为 0。
总结
这道题的关键在于:
- 使用动态规划来表示每一步的选择。
- 将每张卡牌的选择结果转化为对 3 的余数,减少了复杂度。
- 利用状态转移来不断更新方案数,最终得到符合条件的方案数。