卡牌翻面求和问题
问题描述
小M有 张卡牌,每张卡牌的正反面分别写着不同的数字,正面是 ,背面是 。小M希望通过选择每张卡牌的一面,使得所有向上的数字之和可以被3整除。你需要告诉小M,一共有多少种不同的方案可以满足这个条件。由于可能的方案数量过大,结果需要对 取模。
例如:如果有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
代码
MOD = 10**9 + 7
def solution(n: int, a: list, b: list) -> int:
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]
mod1 = num1 % 3
mod2 = num2 % 3
for j in range(3):
dp[i][(j + mod1) % 3] = (dp[i][(j + mod1) % 3] + dp[i - 1][j]) % MOD
dp[i][(j + mod2) % 3] = (dp[i][(j + mod2) % 3] + dp[i - 1][j]) % MOD
return dp[n][0]
if __name__ == '__main__':
print(solution(n = 3, a = [1, 2, 3], b = [2, 3, 2]) == 3)
print(solution(n = 4, a = [3, 1, 2, 4], b = [1, 2, 3, 1]) == 6)
print(solution(n = 5, a = [1, 2, 3, 4, 5], b = [1, 2, 3, 4, 5]) == 32)
代码思路详细解释与分析总结
问题理解
小M有 张卡牌,每张卡牌的正反面分别写着不同的数字,正面是 ,背面是 。我们需要找到所有满足这 张卡牌正面或背面朝上的数字之和可以被3整除的组合数。由于可能的方案数量过大,结果需要对 取模。
数据结构选择
我们选择使用动态规划(Dynamic Programming, DP)来解决这个问题。动态规划是一种通过将问题分解为子问题并存储子问题的解来解决复杂问题的方法。
算法步骤
-
定义状态:
- 我们使用一个二维数组
dp,其中dp[i][j]表示前i张卡牌中,选择某些卡牌使得它们的和模3余j的方案数。 dp[i][j]的含义是:前i张卡牌中,选择某些卡牌使得它们的和模3余j的方案数。
- 我们使用一个二维数组
-
初始化:
dp[0][0] = 1,表示前0张卡牌中,和模3余0的方案数为1(即空集)。
-
状态转移:
- 对于每一张卡牌
i,我们有两个选择:选择正面a[i-1]或选择背面b[i-1]。 - 计算
a[i-1]和b[i-1]分别模3的余数mod1和mod2。 - 对于每个可能的余数
j,更新dp[i][(j + mod1) % 3]和dp[i][(j + mod2) % 3]。
- 对于每一张卡牌
-
结果:
- 最终结果是
dp[n][0],即前n张卡牌中,选择某些卡牌使得它们的和模3余0的方案数。
- 最终结果是
复杂度分析
时间复杂度
- 遍历卡牌:我们需要遍历每张卡牌一次,因此这部分的时间复杂度是 O(n)。
- 更新状态:对于每张卡牌,我们需要更新两个状态(正面和背面),每个状态更新需要常数时间。因此,这部分的时间复杂度是 O(1)。
综合来看,总的时间复杂度是 O(n)。
空间复杂度
- 动态规划数组:我们使用了一个二维数组
dp,其大小为(n+1) * 3,因此这部分的空间复杂度是 O(n)。
综合来看,总的空间复杂度是 O(n)。
优化空间复杂度
- 滚动数组:我们可以通过使用滚动数组的方式将空间复杂度优化到 O(1)。因为我们只需要前一个状态的信息,所以可以只使用一个大小为 3 的数组来存储当前状态,而不需要存储所有状态。