第295题 糖果吃法优化问题

87 阅读4分钟

问题描述

小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规划她在 nn 天内吃糖果的方案,使得在满足规则的情况下,获得最大的糖果美味值。小R的规则包括两点:

  1. 如果今天吃糖果,明天不能吃。
  2. 小R有 kk 次机会打破规则,即可以连续两天都吃糖果。

这道题的本质是一个多维动态规划问题。动态规划擅长解决具有阶段性决策并且状态存在递推关系的问题。

解题思路

1. 状态定义

定义 dp[i][j]dp[i][j] 表示 ii 天结束时,使用了 jj 次机会的最大美味值

  • 阶段: 以天数 ii 为阶段,从第 1 天(i=1i = 1)到第 nn 天(i=ni = n)。
  • 状态变量: jj 表示已使用的打破规则机会数,范围为 [0,k][0, k]
  • 决策: 每一天,小R可以选择“吃”或者“不吃”糖果。如果选择“吃”,需要考虑是否打破规则。

2. 转移方程的推导

对于任意一天 ii 和已使用的机会数 jj,其最大美味值 dp[i][j]dp[i][j]dp[i][j] 取决于以下三种决策:

  1. 今天不吃糖果:

    • 如果今天不吃糖果,那么今天的最大美味值和昨天相同:

    dp[i][j]=dp[i1][j]dp[i][j] = dp[i-1][j]

  2. 今天吃糖果(昨天没吃):

    • 如果今天吃糖果,而昨天没吃糖果,那么可以从 dp[i2][j]dp[i-2][j] 转移,加上今天糖果的美味值:

    dp[i][j]=max(dp[i][j],dp[i2][j]+a[i1])dp[i][j] = \max(dp[i][j], dp[i-2][j] + a[i-1])

  3. 今天吃糖果(昨天也吃):

    • 如果今天吃糖果且昨天也吃了糖果,则需要打破规则消耗一次机会。可以从 dp[i1][j1]dp[i-1][j-1] 转移:

    dp[i][j]=max(dp[i][j],dp[i1][j1]+a[i1])dp[i][j] = \max(dp[i][j], dp[i-1][j-1] + a[i-1])

注意:

  • i2i-2 是因为吃糖果后如果选择不打破规则,则需要隔一天才能再次吃。
  • a[i1]a[i-1] 是第 ii 天糖果的美味值(题目索引从 1 开始,数组从 0 开始)。

3. 初始条件

  1. 第 0 天(哨兵状态): 没有实际意义,因此美味值为 0:

    dp[0][j]=0,j[0,k]dp[0][j] = 0, \forall j \in [0, k]

  2. 第 1 天:

    • 第一天只能选择吃糖果(因为没有前一天的约束):

    dp[1][j]=a[0],j[0,k]dp[1][j] = a[0], \forall j \in [0, k]


4. 结果计算

最终答案是所有状态中,第 nn 天的最大美味值:

result=max(dp[n][j]),j[0,k]{result} = \max(dp[n][j]), \forall j \in [0, k]


5. 动态规划表格的填充

通过定义 dp[i][j]dp[i][j] 和状态转移方程,我们构建一个 (n+1)×(k+1)(n+1) \times (k+1) 的二维动态规划表。通过逐行填表,我们在 O(n×k)O(n \times k) 的时间复杂度内完成求解。


代码实现与注释

以下是完整的代码实现及详细注释:

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+4+6+7=192 + 4 + 6 + 7 = 19

示例 2

输入:n = 6, k = 2, a = [10, 20, 30, 40, 50, 60]

输出:170

具体方案:选择在第2、4、5、6天吃糖果,20+40+50+60=17020 + 40 + 50 + 60 = 170

示例 3

输入:n = 5, k = 0, a = [8, 12, 15, 20, 25]

输出:48

具体方案:选择在第1、3、5天吃糖果,8+15+25=488 + 15 + 25 = 48


总结与优化

时间复杂度分析

  • 时间复杂度: O(n×k)O(n \times k),因为我们需要遍历 nn 天和 kk 次机会。
  • 空间复杂度: O(n×k)O(n \times k),可优化为 O(k)O(k) 使用滚动数组,仅存储当前和前一天的状态。

问题的难点和解决

  1. 决策逻辑: 需要细致处理“昨天是否吃糖果”的不同情况。
  2. 状态转移: 综合考虑不吃、隔天吃和连续两天吃(消耗机会)的情况,确保状态递推完整。

通过动态规划,问题得以高效解决,并具备良好的可扩展性。