问题描述
小C面临一个选择问题:她可以从编号为1到n的糖果中任意选择一些糖果作为奖励,但需要遵守一个限制规则:如果选择了编号为i的糖果,那么编号为i-1、i-2、i+1、i+2的糖果将不能被选择。每个糖果都有一个对应的美味值,小C希望所选糖果的美味值之和最大。
例如:当有7个糖果,编号为1到7,美味值分别为 3, 1, 2, 7, 10, 2, 4,小C可以选择编号为1、4、7的糖果,这样可以得到最大美味值之和为 3 + 7 + 4 = 14。
问题解析
-
问题定义:我们需要从编号为1到n的糖果中选择一些糖果,使得所选糖果的美味值之和最大,同时遵守选择编号为i的糖果后,编号为i-1、i-2、i+1、i+2的糖果不能被选择。
-
状态定义:我们可以定义
dp[i]为考虑前i个糖果时,能够获得的最大美味值之和。 -
状态转移方程:对于每个糖果i,我们不能选择编号为i-1和i-2的糖果,同时选择了编号为i的糖果后,我们也不能选择编号为i+1和i+2的糖果。
-
边界条件:
- 当
n == 0时,没有糖果可选,最大美味值之和为0。 - 当
n == 1时,只能选择编号为1的糖果,最大美味值之和为a[0]。 - 当
n == 2时,可以选择编号为1或2的糖果,最大美味值之和为a[0]和a[1]中的最大值。
- 当
-
填充DP数组:从
i = 3开始,我们根据状态转移方程填充dp数组。对于每个i,我们计算dp[i]的值,它是dp[i-3]、dp[i-4]和dp[i-5]中的最大值加上当前糖果a[i]的美味值。 -
结果:最后,
max(dp)将包含考虑所有糖果时的最大美味值之和。
AC代码
def solution(n: int, a: list) -> int:
# write code here
if n == 0:
return 0
if n == 1:
return a[0]
if n == 2:
return max(a[0], a[1])
# 初始化 dp 数组
dp = [0] * n
dp[0] = a[0]
dp[1] = max(a[1],a[0])
dp[2] = max(a[2],a[1],a[0])
# 填充 dp 数组
for i in range(3, n):
if i==3:
dp[i]=dp[i-3]+a[i]
elif i==4:
dp[i]=max(dp[i-3],dp[i-4])+a[i]
else:
dp[i] = max(dp[i-3], dp[i-4],dp[i-5]) + a[i]
return max(dp)
代码解析
-
边界条件:
- 如果没有糖果(
n == 0),则返回0,因为没有糖果可选。 - 如果只有一个糖果(
n == 1),则返回该糖果的美味值。 - 如果有两个糖果(
n == 2),则返回两个糖果中美味值较大的那个。
- 如果没有糖果(
-
初始化DP数组:
dp[0]被初始化为第一个糖果的美味值。dp[1]被初始化为前两个糖果中美味值较大的那个。dp[2]被初始化为前三个糖果中美味值最大的那个。
-
填充DP数组:
- 从第三个糖果开始,对于每个糖果
i,我们计算dp[i]的值。根据问题的限制,如果选择了编号为i的糖果,那么编号为i-1、i-2、i+1和i+2的糖果都不能被选择。 - 对于
i == 3,dp[3]的值是dp[0](因为i-3是0)加上第三个糖果的美味值。 - 对于
i == 4,dp[4]的值是dp[1](因为i-3是1)和dp[0](因为i-4是0)中的最大值加上第四个糖果的美味值。 - 对于
i > 4,dp[i]的值是dp[i-3]、dp[i-4]和dp[i-5]中的最大值加上第i个糖果的美味值。
- 从第三个糖果开始,对于每个糖果
-
返回结果:
- 代码返回
dp数组中的最大值。
- 代码返回
知识点总结
-
动态规划(Dynamic Programming, DP):动态规划是一种通过将复杂问题分解为更简单的子问题来求解的方法。它通常用于求解具有重叠子问题和最优子结构特性的问题。
-
状态转移方程:在动态规划中,我们需要定义一个状态转移方程,它描述了问题的最优解是如何由子问题的最优解构成的。
-
边界条件:在动态规划中,我们需要定义问题的边界条件,即当问题规模减小时,问题如何解决。
-
最优子结构:问题的最优解包含其子问题的最优解,这是动态规划能够解决的问题的一个关键特性。
-
重叠子问题:在动态规划中,相同的子问题会被多次求解,因此我们可以通过记忆化(memoization)或迭代的方式来避免重复计算。