使用 Python 解题 - 卡牌翻面求和问题

108 阅读3分钟

一,题目详情

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

97899a7a94c0ec1ef8bf2d8eb5048c8.png

二,解题思路

1,问题分析

我们需要计算所有可能的组合中,使得数字之和可以被3整除的方案数。每张卡牌有两种选择(正面或背面),因此总共有 2^n 种组合。

2,算法策略

使用动态规划来解决这个问题。我们用一个数组 current 来表示当前所有可能的余数(0, 1, 2)的方案数。初始时,余数为0的方案数为1(即空集)。

对于每张卡牌,我们计算选择正面或背面后的新余数,并更新 current 数组。

具体步骤如下:

  1. 初始化 current = [1, 0, 0],表示余数0有1种方案。
  2. 遍历每张卡牌,计算选择正面或背面后的新余数。
  3. 更新 current 数组,将新余数对应的方案数加到 current 中。
  4. 最终,current[0] 即为满足条件的方案数。

3,逐步推演(以样例1为例)

输入:n = 3, a = [1, 2, 3], b = [2, 3, 2]

  1. 初始 current = [1, 0, 0]

  2. 第一张卡牌 (1, 2):

    • 选择1:余数为 (0 + 1) % 3 = 1
    • 选择2:余数为 (0 + 2) % 3 = 2
    • current = [0, 1, 1]
  3. 第二张卡牌 (2, 3):

    • 选择2:余数为 (1 + 2) % 3 = 0,(2 + 2) % 3 = 1
    • 选择3:余数为 (1 + 3) % 3 = 1,(2 + 3) % 3 = 2
    • current = [1, 2, 2]
  4. 第三张卡牌 (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)

四,总结

通过动态规划和余数计算,我们实现了:

  1. 高效计算方案数:利用动态规划和模运算
  2. 清晰的逻辑:分步处理每张卡牌
  3. 普适性:适用于所有卡牌数字

这种解法不仅高效,还易于理解和实现。当遇到“需要计算满足特定条件的组合数”类问题时,动态规划和模运算往往是解决问题的关键策略。

69041eff2e19351050d2e2502f36049.png