问题描述
小R不再追求甜点中最高的喜爱值,今天他想要的是甜点喜爱值之和正好匹配他的预期值 S。为了达到这个目标,他可以使用魔法棒来改变甜点的喜爱值,使其变为原来喜爱值的阶乘。每个甜点只能使用一次魔法棒,也可以完全不用。
下午茶小哥今天带来了 N 个甜点,每个甜点都有一个固定的喜爱值。小R有 M 个魔法棒,他可以选择任意甜点使用,但每个甜点只能使用一次魔法棒。他的目标是通过选择一些甜点,可能使用魔法棒,使得这些甜点的喜爱值之和恰好为 S。
请计算小R有多少种不同的方案满足他的要求。如果两种方案中,选择的甜点不同,或者使用魔法棒的甜点不同,则视为不同的方案。 代码解读和实现`def factorial(n): if n == 0 or n == 1: return 1 result = 1 for i in range(2, n + 1): result *= i return result
def solution(n, m, s, like): # 计算阶乘值 factorials = [factorial(x) for x in like]
# 初始化 DP 数组
dp = [[[0 for _ in range(s + 1)] for _ in range(m + 1)] for _ in range(n + 1)]
dp[0][0][0] = 1 # 基础状态
for i in range(1, n + 1):
original = like[i - 1]
fact = factorials[i - 1]
for j in range(m + 1):
for k in range(s + 1):
# 不使用魔法棒
dp[i][j][k] += dp[i - 1][j][k] # 不选这个甜点
if k >= original:
dp[i][j][k] += dp[i - 1][j][k - original] # 选这个甜点,不用魔法
if j > 0 and k >= fact:
dp[i][j][k] += dp[i - 1][j - 1][k - fact] # 选这个甜点,使用魔法
# 汇总所有可能使用的魔法棒数量
return sum(dp[n][j][s] for j in range(m + 1))
-
阶乘计算:
- 使用
factorial函数计算每个甜点的阶乘值,并存储在factorials列表中。
- 使用
-
动态规划数组初始化:
dp[i][j][k]表示在前i个甜点中,使用j个魔法棒,使得喜爱值之和为k的方案数。- 初始化
dp[0][0][0] = 1,表示不选任何甜点且不使用魔法棒时,喜爱值之和为 0 的方案数为 1。
-
状态转移:
- 对于每个甜点
i,可以选择不使用魔法棒、使用魔法棒或不选这个甜点。 - 更新
dp[i][j][k]时,考虑所有可能的情况,并累加相应的方案数。
- 对于每个甜点
-
结果汇总:
-
最终结果是所有可能使用的魔法棒数量
j对应的dp[n][j][s]的和。 -` for i in range(1, n + 1): original = like[i - 1] fact = factorials[i - 1]for j in range(m + 1): for k in range(s + 1): # 不使用魔法棒 dp[i][j][k] += dp[i - 1][j][k] # 不选这个甜点
if k >= original: dp[i][j][k] += dp[i - 1][j][k - original] # 选这个甜点,不用魔法 if j > 0 and k >= fact: dp[i][j][k] += dp[i - 1][j - 1][k - fact] # 选这个甜点,使用魔法`
针对代码解读: 这是一个三重嵌套的循环,外层循环控制遍历每个甜点(从第
1个到第n个,索引为i)。对于每个甜点,先获取它对应的两个属性值:original(从like列表中获取,可能表示选择该甜点本身消耗的资源量等)和fact(从factorials列表中获取,可能与使用魔法棒操作该甜点相关的属性)。 -
然后内部的两层嵌套循环(分别遍历魔法棒数量j和资源量等k的所有可能取值范围)来进行状态转移的计算:
dp[i][j][k] += dp[i - 1][j][k]:这行代码表示在当前考虑第i个甜点时,如果不选择这个甜点(也就是不使用魔法棒去处理它),那么当前状态dp[i][j][k]的方案数量应该等于上一个甜点(即i - 1个甜点)在相同魔法棒数量j和相同资源量k情况下的方案数量,相当于继承了之前的状态。if k >= original:这个条件判断语句下的dp[i][j][k] += dp[i - 1][j][k - original]:表示当当前的资源量k足够选择这个甜点(即大于等于该甜点本身消耗的资源量original)时,在不使用魔法棒的情况下选择这个甜点后的方案数量,应该是在上一个甜点(i - 1个甜点)在相同魔法棒数量j,但资源量剩余为k - original情况下的方案数量基础上累加,也就是增加了选择当前甜点这种新的方案情况。if j > 0 and k >= fact:这个条件判断语句下的dp[i][j][k] += dp[i - 1][j - 1][k - fact]:表示当还有魔法棒可以使用(j > 0)并且当前的资源量k足够满足使用魔法棒操作这个甜点(大于等于对应的阶乘值fact)时,选择这个甜点并且使用魔法棒后的方案数量,应该是在上一个甜点(i - 1个甜点)在少一个魔法棒(j - 1)且资源量剩余为k - fact情况下的方案数量基础上累加,相当于增加了使用魔法棒选择当前甜点这种新的方案情况。 基于marscode ai的辅助提示,我对改代码能进行进一步的更改和加强
-
阶乘计算优化:
- 使用
memo字典来存储已经计算过的阶乘值,避免重复计算。
- 使用
-
动态规划数组优化:
- 使用滚动数组来优化空间复杂度,减少内存使用。通过从后向前遍历,避免覆盖之前的值。
-
def solution(n, m, s, like): # 计算阶乘值,使用优化后的factorial函数 factorials = [factorial(x) for x in like] # 初始化优化后的DP滚动数组,只保留当前和上一层相关状态所需空间 dp = [[0 for _ in range(s + 1)] for _ in range(m + 1)] prev_dp = [[0 for _ in range(s + 1)] for _ in range(m + 1)] prev_dp[0][0] = 1 # 基础状态 for i in range(1, n + 1): original = like[i - 1] fact = factorials[i - 1] # 从后向前遍历,避免覆盖之前还未使用的值 for j in range(m, -1, -1): for k in range(s, -1, -1): dp[j][k] = prev_dp[j][k] # 不使用魔法棒,先继承上一层状态 if k >= original: dp[j][k] += prev_dp[j][k - original] # 选这个甜点,不用魔法 if j > 0 and k >= fact: dp[j][k] += prev_dp[j - 1][k - fact] # 选这个甜点,使用魔法 # 更新prev_dp,为下一轮迭代做准备 prev_dp = [row.copy() for row in dp] # 汇总所有可能使用的魔法棒数量 return sum(prev_dp[j][s] for j in range(m + 1))
一、算法理解与运用能力
- 使用滚动数组来优化空间复杂度,减少内存使用。通过从后向前遍历,避免覆盖之前的值。
-
-
动态规划深入学习:
- 通过对代码中动态规划部分的剖析,深入理解动态规划的核心概念,包括如何定义状态、确定状态转移方程以及设定边界条件等。可以尝试改变问题的一些条件或者规模,重新推导状态转移过程,强化对这一算法解决实际问题的能力。
- 基于此代码,进一步拓展去解决更多不同类型但同样适用动态规划的题目,例如背包问题的变种、最长公共子序列问题的拓展等,通过不断实践加深对动态规划在不同场景下应用的理解,熟练掌握动态规划算法的各种优化技巧。