问题描述
小R在接下来的n天里想要尽可能多地吃糖果。为了保持健康和节省零花钱,她规定如果今天吃了糖果,明天就不能吃。不过,小R允许自己有k次机会打破这个规则,在连续两天都吃糖果。她希望通过合理安排每天是否吃糖果,获得最大的糖果美味值。你的任务是帮助小R规划她的吃糖果方案,以使她吃到的糖果美味值最大。
例如:小R在7天内有 1, 2, 3, 4, 5, 6, 7 的美味值,她允许自己有1次打破原则的机会。最优方案是在第2、4、6天吃糖果,并在第7天使用一次机会继续吃糖果,最大美味值为 2 + 4 + 6 + 7 = 19。
题目分析
本题的核心目标是帮助小R规划她在 天内吃糖果的方案,使得在满足规则的情况下,获得最大的糖果美味值。小R的规则包括两点:
- 如果今天吃糖果,明天不能吃。
- 小R有 次机会打破规则,即可以连续两天都吃糖果。
这道题的本质是一个多维动态规划问题。动态规划擅长解决具有阶段性决策并且状态存在递推关系的问题。
解题思路
1. 状态定义
定义 表示 第 天结束时,使用了 次机会的最大美味值。
- 阶段: 以天数 为阶段,从第 1 天()到第 天()。
- 状态变量: 表示已使用的打破规则机会数,范围为 。
- 决策: 每一天,小R可以选择“吃”或者“不吃”糖果。如果选择“吃”,需要考虑是否打破规则。
2. 转移方程的推导
对于任意一天 和已使用的机会数 ,其最大美味值 dp[i][j]dp[i][j]dp[i][j] 取决于以下三种决策:
-
今天不吃糖果:
- 如果今天不吃糖果,那么今天的最大美味值和昨天相同:
-
今天吃糖果(昨天没吃):
- 如果今天吃糖果,而昨天没吃糖果,那么可以从 转移,加上今天糖果的美味值:
-
今天吃糖果(昨天也吃):
- 如果今天吃糖果且昨天也吃了糖果,则需要打破规则消耗一次机会。可以从 转移:
注意:
- 是因为吃糖果后如果选择不打破规则,则需要隔一天才能再次吃。
- 是第 天糖果的美味值(题目索引从 1 开始,数组从 0 开始)。
3. 初始条件
-
第 0 天(哨兵状态): 没有实际意义,因此美味值为 0:
-
第 1 天:
- 第一天只能选择吃糖果(因为没有前一天的约束):
4. 结果计算
最终答案是所有状态中,第 天的最大美味值:
5. 动态规划表格的填充
通过定义 和状态转移方程,我们构建一个 的二维动态规划表。通过逐行填表,我们在 的时间复杂度内完成求解。
代码实现与注释
以下是完整的代码实现及详细注释:
def solution(n: int, k: int, a: list) -> int:
# 初始化 DP 表:dp[i][j] 表示第 i 天结束时,使用 j 次机会的最大美味值
dp = [[float('-inf')] * (k + 1) for _ in range(n + 1)]
# 初始条件:第 0 天未吃糖果时的美味值为 0
for j in range(k + 1):
dp[0][j] = 0
# 动态规划填表
for i in range(1, n + 1): # 遍历天数
for j in range(k + 1): # 遍历机会次数
if i == 1:
# 第一天只能选择吃糖果
dp[i][j] = a[0]
if i > 1:
# 第 i 天不吃糖果或隔天吃糖果
dp[i][j] = max(dp[i][j], dp[i-1][j], dp[i-2][j] + a[i-1])
if j > 0:
# 第 i 天使用机会吃糖果
dp[i][j] = max(dp[i][j], dp[i-1][j-1] + a[i-1])
# 返回第 n 天的最大美味值
return max(dp[n][j] for j in range(k + 1))
示例详解
示例 1
输入:n = 7, k = 1, a = [1, 2, 3, 4, 5, 6, 7]
输出:
19
具体方案:选择在第2、4、6、7天吃糖果,
示例 2
输入:n = 6, k = 2, a = [10, 20, 30, 40, 50, 60]
输出:
170
具体方案:选择在第2、4、5、6天吃糖果,
示例 3
输入:n = 5, k = 0, a = [8, 12, 15, 20, 25]
输出:
48
具体方案:选择在第1、3、5天吃糖果,
总结与优化
时间复杂度分析
- 时间复杂度: ,因为我们需要遍历 天和 次机会。
- 空间复杂度: ,可优化为 使用滚动数组,仅存储当前和前一天的状态。
问题的难点和解决
- 决策逻辑: 需要细致处理“昨天是否吃糖果”的不同情况。
- 状态转移: 综合考虑不吃、隔天吃和连续两天吃(消耗机会)的情况,确保状态递推完整。
通过动态规划,问题得以高效解决,并具备良好的可扩展性。