题目解析:卡牌翻面求和问题
小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 和 b。每次我们从两张卡牌中选择一个数字。我们需要计算选出的卡牌数字和的余数为 0 的组合方式的个数,即选出的卡牌和除以 3 的余数为 0 的方案数。
解题思路
-
动态规划状态定义
- 定义一个
dp数组,其中dp[r]表示选取若干张卡牌后的所有数字和除以 3 的余数为r的方案数。这里的r取值为0, 1, 2,表示余数为 0、1、2 的情况。 - 初始时,
dp[0] = 1,因为没有选择任何卡牌时,和为 0,显然能被 3 整除;dp[1] = 0和dp[2] = 0,表示没有选择任何卡牌时,余数为 1 或 2 的情况不成立。
- 定义一个
-
状态转移
- 对于每张卡牌
i,有两种选择:要么选择a[i],要么选择b[i]。每次选择时,我们都会更新每个余数的情况。 - 具体来说,假设当前
dp[r]存储的是以余数r结尾的组合数目,如果我们选择a[i],新的余数变为(r + a[i]) % 3,相应地,我们将dp[r]的值加到new_dp[(r + a[i]) % 3]上。同理,如果选择b[i],我们会更新new_dp[(r + b[i]) % 3]。
这种方式通过遍历所有的卡牌,逐步累积并更新每个余数的可能组合。
- 对于每张卡牌
-
最终结果
- 所有卡牌都选择完之后,
dp[0]即为最终的答案,因为我们要求的是所有可能的选择方式中,和除以 3 的余数为 0 的方案数。
- 所有卡牌都选择完之后,
代码解析
pythonCopy Code
def solution(n: int, a: list, b: list) -> int:
MOD = 10**9 + 7 # 用来处理大数,防止溢出
# 初始化动态规划数组
dp = [0, 0, 0]
dp[0] = 1 # 初始时,和为0的方案数为1
for i in range(n):
# 计算当前卡牌选择后的新方案数
new_dp = [0, 0, 0]
for r in range(3):
# 对于每个余数r,可以选择a[i]或b[i]
new_dp[(r + a[i]) % 3] = (new_dp[(r + a[i]) % 3] + dp[r]) % MOD
new_dp[(r + b[i]) % 3] = (new_dp[(r + b[i]) % 3] + dp[r]) % MOD
# 更新dp数组
dp = new_dp
# dp[0]就是我们需要的方案数
return dp[0]
详细解释
-
初始化
dp数组dp[0] = 1:表示在没有选卡牌的情况下,数字和为 0,可以被 3 整除。dp[1] = 0和dp[2] = 0:表示没有选卡牌时,和不能被 3 整除,初始时这两者不可能。
-
动态更新
dp- 对于每一张卡牌,我们通过遍历当前所有可能的余数
r,计算选择当前卡牌a[i]或b[i]后可能的新余数,并更新到new_dp数组中。 new_dp[(r + a[i]) % 3]表示选择a[i]后的余数为(r + a[i]) % 3,并将dp[r]加到对应的新余数位置上。- 同样的操作也应用到
b[i]。
- 对于每一张卡牌,我们通过遍历当前所有可能的余数
-
更新状态
- 遍历完所有卡牌后,
dp就会保存最终所有选卡牌后可能的余数情况。 - 最终返回
dp[0],即和为 0,能够被 3 整除的组合方式数。
- 遍历完所有卡牌后,
-
模运算
- 由于结果可能会非常大,为了避免溢出,所有的累加操作都使用
MOD = 10**9 + 7来进行取模。
- 由于结果可能会非常大,为了避免溢出,所有的累加操作都使用
举例说明
输入 1:
pythonCopy Code
n = 3
a = [1, 2, 3]
b = [2, 3, 2]
- 选择卡牌后的和的余数为 0 的方案数为 3。
输入 2:
pythonCopy Code
n = 4
a = [3, 1, 2, 4]
b = [1, 2, 3, 1]
- 选择卡牌后的和的余数为 0 的方案数为 6。
输入 3:
pythonCopy Code
n = 5
a = [1, 2, 3, 4, 5]
b = [1, 2, 3, 4, 5]
- 选择卡牌后的和的余数为 0 的方案数为 32。
时间复杂度
- 时间复杂度为
O(n),其中n是卡牌的数量。每张卡牌都遍历一次,对于每个卡牌,需要更新 3 个余数的情况,所以每次循环的时间复杂度为O(3),因此总的时间复杂度为O(n)。
空间复杂度
- 空间复杂度为
O(3),即dp数组的大小固定为 3,用来保存余数为 0、1、2 的方案数。
心得:
使用MarsCode AI编写代码让我体验到了编程的便利与高效。AI能够快速生成代码示例,帮助我理解不同编程概念。通过交互式的反馈,我能迅速调整思路,解决问题。同时,MarsCode AI提供的建议让我了解到更多最佳实践,提升了我的编码水平。这种工具不仅节省了时间,还激发了我的创造力,尤其是在处理复杂问题时,AI的支持显得尤为重要。总的来说,MarsCode AI是编程学习和实践中的得力助手。