题目解析
题目描述
小M有 n 张卡牌,每张卡牌的正反面分别写着不同的数字,正面是 a[i],背面是 b[i]。小M希望通过选择每张卡牌的一面,使得所有向上的数字之和可以被 3 整除。你需要告诉小M,一共有多少种不同的方案可以满足这个条件。由于可能的方案数量过大,结果需要对 109+710^9 + 7 取模。
输入输出格式
-
输入:
- 一个整数
n表示卡牌的数量。 - 两个长度为
n的数组a和b,分别表示每张卡牌的正面和背面的数字。
- 一个整数
-
输出:
- 返回一个整数,表示满足条件的方案数,结果对 109+710^9 + 7 取模。
示例
示例1
输入:
n = 3
a = [1, 2, 3]
b = [2, 3, 2]
输出:
3
示例2
输入:
n = 4
a = [3, 1, 2, 4]
b = [1, 2, 3, 1]
输出:
6
示例3
输入:
n = 5
a = [1, 2, 3, 4, 5]
b = [1, 2, 3, 4, 5]
输出:
32
思路分析
这个问题可以通过动态规划来解决。我们定义一个 DP 数组 dp[i][j],表示前 i 张卡牌选择某一面后,所有卡牌的数字之和模 3 等于 j 的方案数。
状态转移
-
初始状态:
dp[0][0] = 1,表示没有卡牌时,数字和为 0 的方案数为 1(即不选任何卡牌)。
-
状态转移:
-
对于每一张卡牌
i,我们可以选择正面a[i-1]或背面b[i-1],这两种选择分别会改变当前和的模 3 值。 -
对于
dp[i][j],我们需要考虑:- 选择正面
a[i-1]:(j + a[i-1]) % 3 - 选择背面
b[i-1]:(j + b[i-1]) % 3
- 选择正面
-
对于每一个选择,将相应的方案数累加到新的状态中。
-
-
目标:
- 最终的目标是
dp[n][0],即选择完所有卡牌后,数字和能被 3 整除的方案数。
- 最终的目标是
动态规划表
dp[i][j]:表示前i张卡牌,数字和模 3 为j的方案数。- 状态转移时,每次选择当前卡牌的正面或背面,根据当前和模 3 的值更新
dp数组。
代码实现
def solution(n: int, a: list, b: list) -> int:
MOD = 10**9 + 7
# 初始化DP数组,dp[i][j]表示前i张卡牌,和模3为j的方案数
dp = [[0] * 3 for _ in range(n + 1)]
dp[0][0] = 1 # 初始状态:0张卡牌,和为0的方案数为1
# 状态转移
for i in range(1, n + 1):
for j in range(3):
# 选择正面
dp[i][(j + a[i - 1]) % 3] = (dp[i][(j + a[i - 1]) % 3] + dp[i - 1][j]) % MOD
# 选择背面
dp[i][(j + b[i - 1]) % 3] = (dp[i][(j + b[i - 1]) % 3] + dp[i - 1][j]) % MOD
# 返回结果,即所有卡牌选择后,数字和能被3整除的方案数
return dp[n][0]
代码说明
-
MOD 常数:由于答案可能很大,所以我们使用 109+710^9 + 7 进行取模,避免溢出。
-
DP 数组初始化:
dp[i][j]表示前i张卡牌,数字和模 3 等于j的方案数。- 初始状态为
dp[0][0] = 1,即没有卡牌时,数字和为 0 的方案数为 1。
-
状态转移:
- 对于每张卡牌
i,可以选择正面a[i-1]或背面b[i-1],然后更新 DP 数组。
- 对于每张卡牌
-
最终结果:
- 最终返回
dp[n][0],即所有卡牌选择完后,数字和能被 3 整除的方案数。
- 最终返回
时间复杂度
- 时间复杂度:
O(n * 3),其中n是卡牌的数量。对于每一张卡牌,我们需要更新三种可能的和模 3 的情况。因此,总时间复杂度是O(n)。 - 空间复杂度:
O(n * 3),用于存储 DP 数组。
边界情况
- 最小输入:如果
n = 1,则只有两种选择(正面或背面),需要检查这两种情况是否满足条件。 - 所有卡牌的正反面数字相同:即所有
a[i] == b[i],此时问题变得简单,只需要选择一种情况。 - 大数情况:即
n很大,确保在计算中对答案进行取模,以避免溢出。
示例分析
示例1
输入:
n = 3, a = [1, 2, 3], b = [2, 3, 2]
输出:
3
解释:通过选择卡牌的正面或背面,可以得到 3 种符合条件的方案。
示例2
输入:
n = 4, a = [3, 1, 2, 4], b = [1, 2, 3, 1]
输出:
6
解释:有 6 种选择,使得数字和模 3 为 0。