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

84 阅读3分钟

题目地址:卡牌翻面求和问题 - MarsCode

问题描述

小M有 n 张卡牌,每张卡牌的正反面分别写着不同的数字,正面是aia_i,背面是bib_i。小M希望通过选择每张卡牌的一面,使得所有向上的数字之和可以被3整除。你需要告诉小M,一共有多少种不同的方案可以满足这个条件。由于可能的方案数量过大,结果需要对 109+710^9+7 取模。

例如:如果有3张卡牌,正反面数字分别为 (1,2)(2,3) 和 (3,2),你需要找到所有满足这3张卡牌正面或背面朝上的数字之和可以被3整除的组合数。

分析

题目要计算组合数,最终要计算n张牌正面或背面朝上的数字之和可以被3整除,而考虑拆解为子问题时,前i张牌可以不要求被3整除,只要保证全部和被3整除,因此考虑使用动态规划,需要记录求和余数为0,1,2的状态。

使用dp[i][j]表示前i张牌选择某些卡牌使得总和模3余 j 的方案数。

初始化dp[0][0]=1.表示没有卡牌时,数字之和为0的方案数为1(空集的情况)。

状态转移: 对于每一张卡牌,它有两种状态:正面朝上或者背面朝上。因此,当我们处理第 i 张卡牌时,可以根据其正面和背面的值来更新 dp 数组。对于当前处理的第i张牌,有正面反面两种选择。

dp[i][(j + a[i-1]) % 3] = (dp[i][(j + a[i-1]) % 3] + dp[i-1][j]) % MOD 
dp[i][(j + b[i-1]) % 3] = (dp[i][(j + b[i-1]) % 3] + dp[i-1][j]) % MOD 

最后返回dp[n][0]

AI分析:

image.png

代码:

def solution(n: int, a: list, b: list) -> int:
    # write code here
    MOD = 1e9+7
    dp = [[0 for _ in range(3)] for _ in range(n + 1)]
    dp[1][a[0]%3]=1
    if a[0] % 3 != b[0] % 3:
        dp[1][b[0] % 3] = 1
    else:
        dp[1][b[0] % 3] = 2 
    for i in range(2, n + 1):
        for j in range(3):
            dp[i][(j + a[i-1]) % 3] = (dp[i][(j + a[i-1]) % 3] + dp[i-1][j]) % MOD
            dp[i][(j + b[i-1]) % 3] = (dp[i][(j + b[i-1]) % 3] + dp[i-1][j]) % MOD
    return dp[n][0]

这里下标从1开始,同时需要注意边界情况。

虽然难度标为难题,但还算比较简单,一次提问AI就能解决。

优化

上述方法时间复杂度为O(n)O(n),空间复杂度为O(n)O(n)

由于每次状态转移只依赖于前一个状态 dp[i-1],我们可以使用滚动数组来优化空间复杂度。 每轮迭代只需记录三个余数下的方案数

def solution(n: int, a: list, b: list) -> int:
    MOD = 10**9 + 7
    dp = [0] * 3
    dp[0] = 1
    
    # 状态转移
    for i in range(1, n + 1):
        new_dp = [0] * 3
        for j in range(3):
            new_dp[(j + a[i-1]) % 3] = (new_dp[(j + a[i-1]) % 3] + dp[j]) % MOD
            new_dp[(j + b[i-1]) % 3] = (new_dp[(j + b[i-1]) % 3] + dp[j]) % MOD
        dp = new_dp
    
    return dp[0]

空间复杂度只需O(1)O(1)

image.png