卡牌翻面求和问题 题解 | 豆包MarsCode AI刷题

59 阅读3分钟

题目反思与总结笔记:卡牌翻面求和问题

题目概述

在这个问题中,小M有 n 张卡牌,每张卡牌的正面和背面上分别写着不同的数字。目标是选择每张卡牌的一面,使得所有卡牌上数字的总和能够被 3 整除。需要计算满足此条件的所有选择方案的数量,并对结果取模 109+710^9 + 7109+7。

解题思路分析

  1. 问题的本质

    • 每张卡牌可以选择正面或背面,因而每个选择会对总和产生不同的影响。
    • 通过动态规划(DP)来记录每种选择方案下的和对 3 的余数。
  2. 状态定义

    • 定义 dp[i][j] 表示前 i 张卡牌中,选择后所有数字和对 3 取余为 j 的方案数。
    • j 可以取值 0、1、2,分别表示和对 3 的余数。
  3. 初始状态

    • dp[0][0] = 1:没有卡牌时,和为 0 的方案只有 1 种(即不选任何卡牌)。
    • 其他状态 dp[0][1]dp[0][2] 初始化为 0。
  4. 状态转移

    • 对于第 i 张卡牌,有两种选择:

      • 选择正面 aia_iai​:更新当前和的余数。
      • 选择背面 bib_ibi​:同样更新当前和的余数。
    • 状态转移公式:

dp[i][j]=(dp[i−1][(j−a[i−1]%3+3)%3]+dp[i−1][(j−b[i−1]%3+3)%3])mod  (109+7)dp[i][j] = (dp[i-1][(j - a[i-1] % 3 + 3) % 3] + dp[i-1][(j - b[i-1] % 3 + 3) % 3]) \mod (10^9 + 7)dp[i][j]=(dp[i−1][(j−a[i−1]%3+3)%3]+dp[i−1][(j−b[i−1]%3+3)%3])mod(109+
dp[i][j]=(dp[i−1][(j−a[i−1]%3+3)%3]+dp[i−1][(j−b[i−1]) %3+3)%3])mod(107)

-   通过对每张卡牌的选择,更新所有可能的余数状态。
  1. 最终结果

    • 需要返回的结果是 dp[n][0],即所有卡牌选择后,和对 3 取余为 0 的方案数。

复杂度分析

  • 时间复杂度:O(n)
    每张卡牌遍历 3 种余数的状态转移,整体复杂度为 O(n)。
  • 空间复杂度:O(n)
    只需要一个大小为 (n+1)×3(n + 1) \times 3(n+1)×3 的二维数组来保存状态。

代码实现

以下是完整的 Python 实现代码:

def solution(n: int, a: list, b: list) -> int:
    MOD = 10**9 + 7
    dp = [[0] * 3 for _ in range(n + 1)]
    dp[0][0] = 1  # 初始状态,没有卡牌时和为0的方案数为1
    
    for i in range(1, n + 1):
        for j in range(3):
            dp[i][j] = (dp[i-1][(j - a[i-1] % 3 + 3) % 3] + dp[i-1][(j - b[i-1] % 3 + 3) % 3]) % 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)

总结

通过动态规划的方法,我们有效地解决了卡牌翻面求和的问题。关键在于正确建立状态并进行转移。在处理此类问题时,关注状态的定义和转移公式是求解的核心。