浅析线性DP问题 | 豆包MarsCode AI刷题

89 阅读4分钟

线性动态规划问题是算法题中占比非常大的一类经典问题。本文将从一道例题引入,逐步分析如何使用线性动态规划方法解决问题,并总结线性DP问题的一些共性特点,帮助读者掌握如何设计线性DP问题的状态,列出状态转移方程并解决问题。

问题描述

小R在接下来的n天里想要尽可能多地吃糖果。为了保持健康和节省零花钱,她规定如果今天吃了糖果,明天就不能吃。不过,小R允许自己有k次机会打破这个规则,在连续两天都吃糖果。她希望通过合理安排每天是否吃糖果,获得最大的糖果美味值。你的任务是帮助小R规划她的吃糖果方案,以使她吃到的糖果美味值最大。

例如:小R在7天内有 1, 2, 3, 4, 5, 6, 7 的美味值,她允许自己有1次打破原则的机会。最优方案是在第2、4、6天吃糖果,并在第7天使用一次机会继续吃糖果,最大美味值为 2 + 4 + 6 + 7 = 19


测试样例

样例1:

输入:n = 7, k = 1, a = [1, 2, 3, 4, 5, 6, 7]
输出:19

样例2:

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

样例3:

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

问题分析

首先,我们从样例就能够看出,使用贪心算法无法解决该问题,因为选择今天是否吃糖果会影响到每天能不能吃糖果。其次,如果使用枚举算法,所有吃糖果的可能选择有2^N种,很明显不可行。因此,我们需要使用其它算法解决问题。

由于题目比较复杂,我们可以尝试先把题目分解为更小的子问题。

  1. 小R每天有哪些可能的选择?显然,对于某一天而言,小R可以选择吃或者不吃糖果。

  2. 小R在今天的选择如何影响明天的选择?如果今天吃了糖果,明天可以选择不吃糖果或使用一次打破规则的机会;如果今天不吃糖果,明天可以选择吃或不吃糖果。

  3. 如何表示某一天小R的状态?由于小R当天可以选择吃不吃糖果,并且可以有打破规则的机会,因此我们可以使用三个数字表示某一天小R的状态:第i天,剩余j次打破规则的机会,当天是否吃糖果s(1为吃糖果,0为不吃糖果)。

解法设计

根据问题分析,我们自然可以得到动态规划的状态定义如下: 设dp[i][j][s]为第i天结束时,剩余j次机会,并且状态为s时的最大美味值,其中:

  • s = 0 表示第i天不吃糖果。
  • s = 1 表示第i天吃糖果。

根据状态定义,可以得到如下的状态转移方程:

  1. i天不吃糖果时,今天的最大值可以是昨天吃或不吃的最大值:dp[i][j][0]=max⁡(dp[i−1][j][0], dp[i−1][j][1])

  2. i天吃糖果时,如果昨天没有吃,则直接吃糖果;如果昨天吃了,且剩余机会大于0,则可以使用一次打破规则的机会继续吃糖果:dp[i][j][1]=max(dp[i−1][j][0], dp[i−1][j+1])+a[i]

代码实现

以下是基于上述解法设计的Java实现:

public class Main {
    public static int solution(int n, int k, int[] a) {
        int[][][] dp = new int[109][109][2];
        dp[0][k][1] = a[0];
        for (int i = 1; i < n; i++) {
            for (int j = 0; j <= k; j++) {
                dp[i][j][0] = Math.max(dp[i - 1][j][0], dp[i - 1][j][1]);
                if (j < k)
                    dp[i][j][1] = Math.max(dp[i - 1][j][0], dp[i - 1][j + 1][1]) + a[i];
                else
                    dp[i][j][1] = dp[i - 1][j][0] + a[i];
            }
        }
        int ans = 0;
        for (int i = 0; i <= k; i++)
            ans = Math.max(ans, Math.max(dp[n - 1][i][0], dp[n - 1][i][1]));
        // System.out.println(ans);
        return ans;
    }
}

算法特点

通过上述问题,我们可以总结出线性动态规划问题的一些共性特点:

  1. 状态的单调性:线性动态规划问题通常以时间或序列为主线,状态依赖于前一时刻或前一位置的状态,如本题中dp[i]只依赖dp[i-1]

  2. 状态定义:根据问题约束条件,状态往往具有多个维度(如本题中的天数i、剩余机会j、是否吃糖果s)。状态的维度是解决问题的关键。

  3. 状态转移依赖于限制条件:状态转移公式是动态规划问题的核心,通常由问题的限制条件决定。本题的限制条件有:不能连续两天吃糖果;允许打破规则。

  4. 目标为最优子结构的组合:动态规划问题通常遵循最优子结构性质,即当前的最优解由历史状态的最优解推导而来。本题中,小R在某一天的最大美味值取决于她前一天的选择。