动态规划
数字分组求偶数和
问题描述
小M面对一组从 1 到 9 的数字,这些数字被分成多个小组,并从每个小组中选择一个数字组成一个新的数。目标是使得这个新数的各位数字之和为偶数。任务是计算出有多少种不同的分组和选择方法可以达到这一目标。
numbers: 一个由多个整数字符串组成的列表,每个字符串可以视为一个数字组。小M需要从每个数字组中选择一个数字。 例如对于[123, 456, 789],14个符合条件的数为:147 149 158 167 169 248 257 259 268 347 349 358 367 369。
测试样例
样例1:
输入:
numbers = [123, 456, 789]
输出:14
样例2:
输入:
numbers = [123456789]
输出:4
样例3:
输入:
numbers = [14329, 7568]
输出:10
解题思路
- 转换为状态问题:我们不需要生成所有可能的组合,而是可以只记录当前形成的数中奇数个数的奇偶性,从而判断这个数的和是否为偶数。这样可以大大减少计算量。
- 统计每组的奇偶数个数:对于每个数字组,计算其中奇数和偶数的数量。因为每次只能从一个组中选一个数字,这些数量可以帮助我们推断出选取该组中的一个奇数或偶数后,奇数个数的状态变化情况。
- 动态规划转移:设
dp[0]表示当前奇数个数为偶数的情况数量,dp[1]表示当前奇数个数为奇数的情况数量。随着选择每组数字中的一个数,我们只需要更新这两种状态,以判断新生成数的奇数和偶数组合数。
算法步骤
- 统计每个数字组的奇偶数个数
- 初始化
dp数组 - 动态规划更新
dp - 返回结果
代码
def solution(numbers):
# 记录每个数字组中奇数和偶数的数量
groups = []
for group in numbers:
# 将 group 当作字符串处理
group = str(group)
odd_count = sum(1 for digit in group if int(digit) % 2 == 1)
even_count = len(group) - odd_count
groups.append((odd_count, even_count))
# dp[0]表示奇数个数为偶数的情况数量
# dp[1]表示奇数个数为奇数的情况数量
dp = [1, 0]
for odd_count, even_count in groups:
# 更新dp数组
new_dp = [0, 0]
# 奇数个数保持偶数状态:
new_dp[0] = dp[0] * even_count + dp[1] * odd_count
# 奇数个数转为奇数状态:
new_dp[1] = dp[0] * odd_count + dp[1] * even_count
dp = new_dp
# 返回奇数个数为偶数的情况数量
return dp[0]
- 时间复杂度:
O(n) - 空间复杂度:
O(1)
卡牌翻面求和问题
问题描述
小M有 n 张卡牌,每张卡牌的正反面分别写着不同的数字,正面是 ai,背面是 bi。小M希望通过选择每张卡牌的一面,使得所有向上的数字之和可以被3整除。你需要告诉小M,一共有多少种不同的方案可以满足这个条件。由于可能的方案数量过大,结果需要对 10^9+7 取模。
例如:如果有3张卡牌,正反面数字分别为 (1,2),(2,3) 和 (3,2),你需要找到所有满足这3张卡牌正面或背面朝上的数字之和可以被3整除的组合数。
测试样例
样例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
解题思路
- 余数分类:
- 我们只需要关心数字之和对3的余数,因此可以将总和模3的可能性分为3种情况:分别为0、1、2。
- 对于每张卡牌,选择正面或反面后会产生一个数,这个数的模3余数会影响总和的模3余数。
- 动态规划状态定义:
- 设
dp[i][j]表示前i张卡牌中,总和模3为j的方案数。 - 初始状态为
dp[0][0] = 1,表示当没有卡牌时,总和为0的情况有1种方案(即空方案)。
- 设
- 状态转移:
- 对于每张卡牌,正面和反面各有一个数字。
- 设正面数字
a[i]对3的余数为rem_a,反面数字b[i]对3的余数为rem_b。 - 使用前
i-1张卡牌的方案数更新第i张卡牌的状态:- 对于当前总和模3为
k的方案,加入a[i]后总和变为(k + rem_a) % 3。 - 对于当前总和模3为
k的方案,加入b[i]后总和变为(k + rem_b) % 3。
- 对于当前总和模3为
- 对于每种情况,我们将方案数累加到对应的
dp[i][new_rem]中。
- 结果:前 n 张卡牌中总和能被3整除的方案数。
算法步骤
- 初始化
dp数组,其中dp[0][0] = 1,其他dp[0][1]和dp[0][2]都为0。 - 遍历每张卡牌,对于每张卡牌:
- 计算其正面和反面的模3余数
- 更新
dp数组,记录选择当前卡牌正面或反面后的状态。
- 取
dp[n][0]的值作为答案,并对结果取模。
代码
def solution(n: int, a: list, b: list) -> int:
# write code here
MOD = 10**9 + 7
# 初始化dp数组
dp = [[0] * 3 for _ in range(n + 1)]
dp[0][0] = 1
# 状态转移
for i in range(1, n + 1):
for j in range(3):
# 选择正面a[i-1]
dp[i][j] += dp[i-1][(j - a[i-1]) % 3]
# 选择背面b[i-1]
dp[i][j] += dp[i-1][(j - b[i-1]) % 3]
dp[i][j] %= MOD
# 返回结果
return dp[n][0]
- 时间复杂度:
O(n) - 空间复杂度:
O(n)