一,题目详情
1,问题描述
小M有 n 张卡牌,每张卡牌的正反面分别写着不同的数字,正面是 a_i,背面是 b_i。小M希望通过选择每张卡牌的一面,使得所有向上的数字之和可以被3整除。你需要告诉小M,一共有多少种不同的方案可以满足这个条件。由于可能的方案数量过大,结果需要对 10^9 + 7 取模。
例如:如果有3张卡牌,正反面数字分别为 (1,2),(2,3) 和 (3,2),你需要找到所有满足这3张卡牌正面或背面朝上的数字之和可以被3整除的组合数。
2,测试样例
样例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
二,解题思路
1,问题分析
我们需要计算所有可能的组合中,使得数字之和可以被3整除的方案数。每张卡牌有两种选择(正面或背面),因此总共有 2^n 种组合。
2,算法策略
使用动态规划来解决这个问题。我们用一个数组 current 来表示当前所有可能的余数(0, 1, 2)的方案数。初始时,余数为0的方案数为1(即空集)。
对于每张卡牌,我们计算选择正面或背面后的新余数,并更新 current 数组。
具体步骤如下:
- 初始化 current = [1, 0, 0],表示余数0有1种方案。
- 遍历每张卡牌,计算选择正面或背面后的新余数。
- 更新 current 数组,将新余数对应的方案数加到 current 中。
- 最终,current[0] 即为满足条件的方案数。
3,逐步推演(以样例1为例)
输入:n = 3, a = [1, 2, 3], b = [2, 3, 2]
-
初始 current = [1, 0, 0]
-
第一张卡牌 (1, 2):
- 选择1:余数为 (0 + 1) % 3 = 1
- 选择2:余数为 (0 + 2) % 3 = 2
- current = [0, 1, 1]
-
第二张卡牌 (2, 3):
- 选择2:余数为 (1 + 2) % 3 = 0,(2 + 2) % 3 = 1
- 选择3:余数为 (1 + 3) % 3 = 1,(2 + 3) % 3 = 2
- current = [1, 2, 2]
-
第三张卡牌 (3, 2):
- 选择3:余数为 (0 + 3) % 3 = 0,(1 + 3) % 3 = 1,(2 + 3) % 3 = 2
- 选择2:余数为 (0 + 2) % 3 = 2,(1 + 2) % 3 = 0,(2 + 2) % 3 = 1
- current = [3, 3, 3]
最终结果为 current[0] = 3。
三,代码实现
def solution(n: int, a: list, b: list) -> int:
MOD = 10**9 + 7
current = [1, 0, 0] # 初始状态:余数0有1种方案
for i in range(n):
ai_mod = a[i] % 3
bi_mod = b[i] % 3
next_ = [0, 0, 0]
for j in range(3):
if current[j] == 0:
continue
# 选择a[i]
new_j_a = (j + ai_mod) % 3
next_[new_j_a] = (next_[new_j_a] + current[j]) % MOD
# 选择b[i]
new_j_b = (j + bi_mod) % 3
next_[new_j_b] = (next_[new_j_b] + current[j]) % MOD
current = next_
return current[0] % MOD
1,复杂度分析
-
时间复杂度:O(n)
- 遍历每张卡牌一次
-
空间复杂度:O(1)
- 仅使用常数级额外空间
2,边界测试
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)
# 边界测试:n=1
print(solution(n = 1, a = [1], b = [2]) == 1)
# 边界测试:所有数字相同
print(solution(n = 3, a = [1, 1, 1], b = [1, 1, 1]) == 0)
四,总结
通过动态规划和余数计算,我们实现了:
- 高效计算方案数:利用动态规划和模运算
- 清晰的逻辑:分步处理每张卡牌
- 普适性:适用于所有卡牌数字
这种解法不仅高效,还易于理解和实现。当遇到“需要计算满足特定条件的组合数”类问题时,动态规划和模运算往往是解决问题的关键策略。