问题描述
小M有 nn 张卡牌,每张卡牌的正反面分别写着不同的数字,正面是 aiai,背面是 bibi。小M希望通过选择每张卡牌的一面,使得所有向上的数字之和可以被3整除。你需要告诉小M,一共有多少种不同的方案可以满足这个条件。由于可能的方案数量过大,结果需要对 109+7109+7 取模。
例如:如果有3张卡牌,正反面数字分别为 (1,2),(2,3) 和 (3,2),你需要找到所有满足这3张卡牌正面或背面朝上的数字之和可以被3整除的组合数。
测试样例
样例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
题目分析
给定 n 张卡牌,每张卡牌有正反两面,分别标有数字 a[i] 和 b[i],我们可以选择卡牌的正面或反面,使得所有卡牌朝上的数字之和能够被 3 整除。我们需要求出所有满足这个条件的不同选择方案数。
解答思路
这道题目的关键点在于每张卡牌可以选择正面 a[i] 或背面 b[i],从而组成不同的方案。并且对于每种方案,数字的和必须能够被 3 整除。
这个问题可以通过 动态规划 来求解。核心思想是考虑卡牌的选择会影响到当前的和对 3 的余数,最后我们只关心和对 3 的余数为 0 的方案数。
代码分析
- 初始化dp数组,dp[i][j]表示前i张卡牌中,选择某些卡牌使得它们的和模3等于j的方案数。 不选择任何卡牌时,和为0的方案数为1。
dp = [[0] * 3 for _ in range(n + 1)]
dp[0][0] = 1
- 遍历每张卡牌。
for i in range(1, n + 1):
num1 = a[i - 1]
num2 = b[i - 1]
- 更新dp数组,包括选择正面的情况和选择背面的情况。
for j in range(3):
dp[i][j] = (dp[i][j] + dp[i - 1][(j - num1 % 3 + 3) % 3]) % MOD
dp[i][j] = (dp[i][j] + dp[i - 1][(j - num2 % 3 + 3) % 3]) % MOD
- 最终答案为dp[n][0],即前n张卡牌中,选择某些卡牌使得它们的和模3等于0的方案数。
return dp[n][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):
num1 = a[i - 1]
num2 = b[i - 1]
for j in range(3):
dp[i][j] = (dp[i][j] + dp[i - 1][(j - num1 % 3 + 3) % 3]) % MOD
dp[i][j] = (dp[i][j] + dp[i - 1][(j - num2 % 3 + 3) % 3]) % MOD
return dp[n][0]
总结
时间复杂度:对于每张卡牌,我们遍历了 3 个可能的余数(0、1、2),因此每张卡牌的处理需要 O(3) 的时间。总的时间复杂度是 O(n),其中 n 是卡牌的数量。
空间复杂度:需要维护一个大小为 3 的 dp 数组,因此空间复杂度是 O(1),即常数空间。
这道题的核心是通过动态规划计算所有合法的卡牌选择方案数,其中每个选择对结果的影响仅限于更新当前的余数状态。通过巧妙地设计状态转移,可以高效地解决问题,并且用取模操作确保结果不超过指定范围。