题目地址:卡牌翻面求和问题 - MarsCode
问题描述
小M有 n 张卡牌,每张卡牌的正反面分别写着不同的数字,正面是,背面是。小M希望通过选择每张卡牌的一面,使得所有向上的数字之和可以被3整除。你需要告诉小M,一共有多少种不同的方案可以满足这个条件。由于可能的方案数量过大,结果需要对 取模。
例如:如果有3张卡牌,正反面数字分别为 (1,2),(2,3) 和 (3,2),你需要找到所有满足这3张卡牌正面或背面朝上的数字之和可以被3整除的组合数。
分析
题目要计算组合数,最终要计算n张牌正面或背面朝上的数字之和可以被3整除,而考虑拆解为子问题时,前i张牌可以不要求被3整除,只要保证全部和被3整除,因此考虑使用动态规划,需要记录求和余数为0,1,2的状态。
使用dp[i][j]表示前i张牌选择某些卡牌使得总和模3余 j 的方案数。
初始化dp[0][0]=1.表示没有卡牌时,数字之和为0的方案数为1(空集的情况)。
状态转移:
对于每一张卡牌,它有两种状态:正面朝上或者背面朝上。因此,当我们处理第 i 张卡牌时,可以根据其正面和背面的值来更新 dp 数组。对于当前处理的第i张牌,有正面反面两种选择。
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
最后返回dp[n][0]
AI分析:
代码:
def solution(n: int, a: list, b: list) -> int:
# write code here
MOD = 1e9+7
dp = [[0 for _ in range(3)] for _ in range(n + 1)]
dp[1][a[0]%3]=1
if a[0] % 3 != b[0] % 3:
dp[1][b[0] % 3] = 1
else:
dp[1][b[0] % 3] = 2
for i in range(2, 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
return dp[n][0]
这里下标从1开始,同时需要注意边界情况。
虽然难度标为难题,但还算比较简单,一次提问AI就能解决。
优化
上述方法时间复杂度为,空间复杂度为。
由于每次状态转移只依赖于前一个状态 dp[i-1],我们可以使用滚动数组来优化空间复杂度。
每轮迭代只需记录三个余数下的方案数
def solution(n: int, a: list, b: list) -> int:
MOD = 10**9 + 7
dp = [0] * 3
dp[0] = 1
# 状态转移
for i in range(1, n + 1):
new_dp = [0] * 3
for j in range(3):
new_dp[(j + a[i-1]) % 3] = (new_dp[(j + a[i-1]) % 3] + dp[j]) % MOD
new_dp[(j + b[i-1]) % 3] = (new_dp[(j + b[i-1]) % 3] + dp[j]) % MOD
dp = new_dp
return dp[0]
空间复杂度只需