问题描述
小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 = 7, a = [3, 1, 2, 7, 10, 2, 4]
输出:14
样例2:
输入:
n = 5, a = [1, 10, 2, 5, 8]
输出:18
样例3:
输入:
n = 6, a = [4, 5, 6, 1, 2, 3]
输出:9
分析
算法步骤
-
定义状态:
- 使用一个数组
dp,其中dp[i]表示在前i个糖果中可以获得的最大美味值。
- 使用一个数组
-
状态转移方程:
-
对于每个糖果
i,我们有两种选择:- 选择第
i个糖果,那么我们不能选择i-1和i-2的糖果,因此dp[i] = a[i] + dp[i-3]。 - 不选择第
i个糖果,那么dp[i] = dp[i-1]。
- 选择第
-
因此,状态转移方程为:
-
dp[i] = max(dp[i-1], a[i] + dp[i-3])
-
-
初始化:
-
我们需要初始化前几个状态,因为状态转移方程依赖于
dp[i-3]。dp[0] = a[0]dp[1] = max(a[0], a[1])dp[2] = max(a[0], a[1], a[2])
-
-
填充dp数组:
- 使用循环从
i=3开始,直到n-1,填充dp数组。
- 使用循环从
-
返回结果:
- 最终结果存储在
dp数组的最后一个位置,即dp[n-1]。
- 最终结果存储在
难点分析
-
状态转移方程的理解:
- 理解为什么选择第
i个糖果时,不能选择i-1和i-2的糖果,并且需要加上dp[i-3]的美味值。 - 理解为什么不选择第
i个糖果时,直接继承dp[i-1]的值。
- 理解为什么选择第
-
初始化的正确性:
- 确保前几个状态的初始化正确,因为状态转移方程依赖于这些初始值。
-
边界条件的处理:
- 处理
n为 0、1、2 的特殊情况,因为这些情况下状态转移方程不适用。
- 处理
-
动态规划数组的填充:
- 确保在填充
dp数组时,正确地应用状态转移方程,避免遗漏或错误的状态更新。
- 确保在填充
代码
def solution(n: int, a: list) -> int:
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[0], a[1])
dp[2] = max(a[0], a[1], a[2])
# 填充dp数组
for i in range(3, n):
dp[i] = max(dp[i-1], a[i] + dp[i-3])
return dp[-1]
if __name__ == '__main__':
print(solution(7, [3, 1, 2, 7, 10, 2, 4]) == 14)
print(solution(5, [1, 10, 2, 5, 8]) == 18)
print(solution(6, [4, 5, 6, 1, 2, 3]) == 9)
代码分析
-
边界条件处理:
- 代码处理了
n为 0、1、2 的特殊情况。 - 当
n == 0时,返回 0。 - 当
n == 1时,返回a[0]。 - 当
n == 2时,返回max(a[0], a[1])。
- 代码处理了
-
初始化:
-
代码初始化了
dp数组的前几个值:dp[0] = a[0]dp[1] = max(a[0], a[1])dp[2] = max(a[0], a[1], a[2])
-
-
状态转移方程:
- 代码使用了状态转移方程:
dp[i] = max(dp[i-1], a[i] + dp[i-3])
- 这表示在每个位置 `i`,我们选择当前糖果 `a[i]` 并加上 `dp[i-3]` 的美味值,或者不选择当前糖果,继承 `dp[i-1]` 的值。
-
填充dp数组:
- 代码使用循环从
i=3开始,直到n-1,正确填充了dp数组。
- 代码使用循环从
-
返回结果:
- 代码返回
dp数组的最后一个值dp[-1],即在前n个糖果中可以获得的最大美味值。
- 代码返回