前言
和AI工具交互,我们要摆正自己的目的,不仅仅是冲着正确的答案去,更重要的是要在过程中,问出最“合适”的问题,下面我将以简单题 “数字分组求偶数和”为例,来介绍MarsCode是如何帮助我刷题的,以及动态规划思考过程中,逐步递进的过程。
问题描述
小M面对一组从 1 到 9 的数字,这些数字被分成多个小组,并从每个小组中选择一个数字组成一个新的数。目标是使得这个新数的各位数字之和为偶数。任务是计算出有多少种不同的分组和选择方法可以达到这一目标。
numbers: 一个由多个整数字符串组成的列表,每个字符串可以视为一个数字组。小M需要从每个数字组中选择一个数字。 例如对于[123, 456, 789],14个符合条件的数为:147 149 158 167 169 248 257 259 268 347 349 358 367 369。
没有头绪?没关系
拿到这个题目,最原始的算法当然就是逐个遍历列表中的字符串的每一个字符进行组合,看相加结果是否满足要求,考虑到数字只有0~9,算法实际运算的时间消耗并不大。但这真的是终点了么?
可以使用 Marscode AI来给我们一些头绪
它提到可以用递归,用它给的代码确实也同样可以过测试用例,但看到命令行明显卡顿了一下,我知道,可能计算的算法效率还不够高。
Ask the right question
算法没有最优,只有更优。既然已经使用的递归,我们知道,在递归过程中,其实会重复计算很多中间结果,在这个例子中,则是在挑选第i组中的数字时,其相加和的奇偶性取决于前面已经挑选的i-1组中的数字的奇偶,所以我们使用如下提示词来避免重复计算:
开辟与可变参数有关的表结构进行存储,并初始化。将该结构当作参数传递给函数,相应地修改递归代码
Marscode也提示我们它发现的规律:
-
状态定义:
- 定义
dp[i][sum]表示从前i个数字组中选择数字,使得各位数字之和为sum的组合数。
- 定义
-
状态转移方程:
- 对于每个数字组
numbers[i],遍历其中的每个数字digit,更新dp[i+1][sum + digit]。
- 对于每个数字组
-
初始状态:
dp[0][0] = 1,表示从前 0 个数字组中选择数字,使得各位数字之和为 0 的组合数为 1。
-
最终结果:
- 统计所有
dp[len(numbers)][sum]中sum为偶数的组合数。
- 统计所有
最后优化一下代码顺利通过(考虑到代码部分占比应尽量低,仅粘贴最终动态规划代码)
def solution(numbers):
# Please write your code here
# 动态规划
n = len(numbers) # 新数字长度,待选组数
dp = [[0, 0] for i in range(n + 1)] # 初始化
dp[0][0] = 1 # 前0组选择奇数的个数为偶数(用0表示)的数量为1
for i, number in enumerate(numbers, start = 1): # 遍历numbers
nums = [int(num) for num in list(str(number))] # 将123转换为[1,2,3]列表
# 判断奇偶个数
odd = 0
for num in nums:
if num % 2 == 1:
odd += 1
even = len(nums) - odd
dp[i][0] = dp[i - 1][0] * even + dp[i - 1][1] * odd # 前i组选择了偶数个奇数的组合数=前i-1组选择了偶数个奇数的组合*当前组的偶数数量+前i-1组选择了奇数个奇数的组合*当前组的奇数数量
dp[i][1] = dp[i - 1][0] * odd + dp[i - 1][1] * even
return dp[n][0] # 返回的是前n组选择了偶数个奇数的组合情况