AI刷题(一) | 豆包MarsCode AI刷题

127 阅读5分钟

动态规划


数字分组求偶数和

问题描述

小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] 表示当前奇数个数为奇数的情况数量。随着选择每组数字中的一个数,我们只需要更新这两种状态,以判断新生成数的奇数和偶数组合数。

算法步骤

  1. 统计每个数字组的奇偶数个数
  2. 初始化 dp 数组
  3. 动态规划更新 dp
  4. 返回结果

代码

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
    • 对于每种情况,我们将方案数累加到对应的 dp[i][new_rem] 中。
  • 结果:前 n 张卡牌中总和能被3整除的方案数。

算法步骤

  1. 初始化 dp 数组,其中 dp[0][0] = 1,其他 dp[0][1]dp[0][2] 都为0。
  2. 遍历每张卡牌,对于每张卡牌:
    • 计算其正面和反面的模3余数
    • 更新 dp 数组,记录选择当前卡牌正面或反面后的状态。
  3. 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)