一、问题描述
小M有 张卡牌,每张卡牌的正反面分别写着不同的数字,正面是 ,背面是 。小M希望通过选择每张卡牌的一面,使得所有向上的数字之和可以被3整除。你需要告诉小M,一共有多少种不同的方案可以满足这个条件。由于可能的方案数量过大,结果需要对取模。
这是一道动态规划和模运算相结合的典型问题,考察了对状态转移的设计能力和对约束条件的处理技巧。
二、解题思路
1. 关键分析
- 模3的性质:对于一个数字 ,它对3取模的结果只有三种可能:、、。我们希望最终选择的数字总和模3等于0。
- 动态规划:
- 状态定义: 表示当前卡牌选择后,模3等于 的方案数。
- 状态转移:当处理第 张卡牌时,我们有两种选择:选择正面或反面。两种选择分别更新对应模值下的方案数。
- 初始状态:如果没有卡牌,只有一种方案,即总和为0(模3等于0),因此 ,其余状态为0。
2. 动态规划转移方程
设第 张卡牌的正反面分别为 和 ,它们对3的余数分别为 和 。更新 数组时:
我们用一个新数组来记录当前卡牌的选择对状态的更新,避免直接修改 影响后续计算。
三、滚动数组优化
在原始方法中, 记录前 张卡牌的状态,每次计算第 张卡牌时需要使用前一层的所有状态。然而,观察状态转移方程发现,更新仅依赖于前一状态。我们可以通过 滚动数组 优化,将二维数组 压缩为一维数组 。
具体做法如下:
- 在每次处理一张卡牌时,临时记录新的状态 。
- 更新完当前卡牌对应的 后,将 替换为 。
- 每次只需维护一个固定大小为3的数组,从而将空间复杂度优化为 。
四、代码实现
下面是用 Python 实现的滚动数组优化代码:
mod = 10**9 + 7
def solution(n: int, a: list, b: list) -> int:
dp = [1, 0, 0]
for r_a, r_b in zip(a, b):
new_dp = [0] * 3
for j in range(3):
new_dp[(j + r_a) % 3] = (new_dp[(j + r_a) % 3] + dp[j]) % mod
new_dp[(j + r_b) % 3] = (new_dp[(j + r_b) % 3] + dp[j]) % mod
dp = new_dp
return dp[0]
五、复杂度分析
1. 时间复杂度
- 遍历卡牌:对于每张卡牌,我们需要进行一次状态更新,总共有 张卡牌,因此遍历卡牌的时间复杂度是 。
- 更新状态:每张卡牌的状态转移中,涉及三个模值的更新,即 ,每次更新是常数时间 。因此,单张卡牌的状态更新时间复杂度为 。
- 总体时间复杂度:综合起来,总体时间复杂度为 。
2. 空间复杂度
- 在使用滚动数组优化后,我们仅需维护一个长度为3的数组 来记录当前状态。每次更新时临时使用一个同样大小的 数组,因此空间复杂度为 ,我们未引入其他复杂的数据结构,因此空间复杂度保持在 。