DAY42

78 阅读18分钟

动规总结

1. 基础问题

在 LeetCode 上的这些动态规划题目中,dp 数组的含义虽然具体情况有所不同,但它们有一些共同点。一般来说,dp[i] 表示「以某种状态到达第 i 个位置时的最优解」,这个「最优解」可以是不同的问题目标,比如最大值、最小值或方案数。

1. LeetCode 509: 斐波那契数

  • dp 数组含义: dp[i] 表示斐波那契数列中第 i 个数字的值。
  • 状态转移方程: dp[i] = dp[i-1] + dp[i-2]
  • 含义: 通过前两个数递推得到当前数的最优解(即值)。

2. LeetCode 70: 爬楼梯

  • dp 数组含义: dp[i] 表示到达第 i 级楼梯的方法总数。
  • 状态转移方程: dp[i] = dp[i-1] + dp[i-2]
  • 含义: 从前一阶或者前两阶楼梯跳过来,得到到达 i 级楼梯的总方案数。

3. LeetCode 746: 使用最小花费爬楼梯

  • dp 数组含义: dp[i] 表示到达第 i 级楼梯所需的最小花费。
  • 状态转移方程: dp[i] = min(dp[i-1], dp[i-2]) + cost[i]
  • 含义: 从前一阶或前两阶楼梯跳过来,选择代价较小的那个,再加上当前楼梯的花费,求得最小总花费。

4. LeetCode 62: 不同路径

  • dp 数组含义: dp[i][j] 表示到达坐标 (i, j) 的路径总数。
  • 状态转移方程: dp[i][j] = dp[i-1][j] + dp[i][j-1]
  • 含义: 到达某个坐标可以从上方或左方过来,因此路径数是它们的和。

5. LeetCode 63: 不同路径 II

  • dp 数组含义: dp[i][j] 表示到达坐标 (i, j) 且避开障碍的路径总数。
  • 状态转移方程: 如果有障碍物,dp[i][j] = 0;否则,dp[i][j] = dp[i-1][j] + dp[i][j-1]
  • 含义: 和第 62 题类似,但需要考虑障碍物的影响。

6. LeetCode 343: 整数拆分

  • dp 数组含义: dp[i] 表示数字 i 的整数拆分的最大乘积。
  • 状态转移方程: dp[i] = max(dp[i], max(j * (i-j), j * dp[i-j])),其中 j 是拆分出的第一个整数。
  • 含义: 数字 i 可以拆分为两个部分,然后取这两个部分的乘积,递推出最大乘积。

7. LeetCode 96: 不同的二叉搜索树

  • dp 数组含义: dp[i] 表示节点数为 i 时,不同的二叉搜索树(BST)的个数。
  • 状态转移方程: dp[i] += dp[j-1] * dp[i-j]j 是当前选择作为根节点的数。
  • 含义: 每个数字都可以作为根节点,左右子树分别由它之前的数字和它之后的数字组成。

共同点总结

  • dp[i] 的含义都是:在第 i 个位置或以第 i 为条件时,达到某个「最优解」的结果。这个最优解可以是路径数、最小值、最大值或方案数。
  • 递推性质:几乎每道题的 dp[i] 都是通过前面的状态(如 dp[i-1], dp[i-2], dp[j] 等)计算得到的,体现了动态规划的「最优子结构」特性。
  • 累积递推dp[i] 通常是依赖之前的 dp 状态逐步累积结果,体现出自底向上的求解方式。

这些题目都是典型的动态规划问题,主要是通过之前的结果(最优子结构)来推导出当前的解,进而逐步求解完整问题。

2. 01背包

LeetCode 416、1049、494、474 这几道题的 dp 数组尽管背景不同,但它们都有一个核心的共同点:它们是基于背包问题的动态规划变种。在这些问题中,dp 数组通常用于记录和表示特定容量或状态下的「最大可能性」或「最优解」。具体来说,dp[i]dp[i][j] 通常表示 某个条件下的可达性、最优解是否能构成特定的目标

1. LeetCode 416: 分割等和子集

  • dp 数组含义: dp[i] 表示能否从数组中选出若干个元素,使其和恰好为 i
  • 状态转移方程: 对于每个数字 num,如果 dp[i-num] 为 true,则 dp[i] 也为 true,表示我们可以通过加入 num 得到和为 i 的子集。
  • 含义: 该问题是一个典型的「0-1 背包问题」,dp[i] 表示能否通过某些选取使得总和恰好为 i

2. LeetCode 1049: 最后一块石头的重量 II

  • dp 数组含义: dp[i] 表示是否能够达到总和为 i 的子集。
  • 状态转移方程: 和 416 类似,dp[i] = dp[i - stone],表示是否可以通过选取某些石头得到重量 i
  • 含义: 本质上和 416 类似,是一个「0-1 背包问题」,求两堆石头重量差最小的问题。

3. LeetCode 494: 目标和

  • dp 数组含义: dp[i] 表示从数组中选取若干元素并加上正负号后能达到和为 i 的方法总数。
  • 状态转移方程: dp[i] = dp[i - num] + dp[i + num],表示通过正负号的组合,计算能否达到某个目标和。
  • 含义: 这是一个「可正负符号的背包问题」,dp[i] 记录某个和是否能通过数组的组合实现。

4. LeetCode 474: 一和零

  • dp 数组含义: dp[i][j] 表示使用 i 个 0 和 j 个 1,能组成的字符串的最大个数。
  • 状态转移方程: dp[i][j] = max(dp[i][j], dp[i-zeros][j-ones] + 1),表示当前的字符串是否可以在已有的组合上扩展。
  • 含义: 这是一个典型的「二维背包问题」,dp[i][j] 记录在特定数量的 0 和 1 的约束下可以组合出的最多的字符串个数。

共同点总结

  1. 背包问题模型: 这几道题目本质上都可以归结为「0-1 背包」问题或其变种(如二维背包问题)。dp 数组用来表示在给定条件下是否能够构成某个目标或实现某个最优解。
  2. dp[i]dp[i][j] 的含义: 它们通常表示「在某种状态或容量下」的可达性、最大化结果或方案总数。例如,dp[i] 可以表示能否选取一些元素使得总和为 i,或者使用一定数量的资源达到某个目标。
  3. 状态转移方程: 通过遍历每个数字或元素,动态更新 dp 数组,表示在当前状态的基础上如何通过选取新的元素实现更新,逐步达到目标。

总结

  • 在这些问题中,dp 数组的核心思路都是通过累积某些条件或状态(如容量、总和、资源数量),不断更新来找到一个最优解,类似于背包问题中填充背包的过程。

3. 完全背包

LeetCode 518、377、322、279 和 139 这几道题的 dp 数组都有一个共同的性质:它们都用于求解「构成目标数的最优解」或「方案数」。在这些题目中,dp[i] 通常表示「达到目标 i 的最优方案」或「达到目标 i 的方案总数」。具体来说,dp[i] 表示的是如何通过不同的方式或选取一些元素组合,来达到目标数 i 的一种最优解或总方案数。

1. LeetCode 518: 零钱兑换 II

  • dp 数组含义: dp[i] 表示使用任意给定的硬币组合成金额 i 的不同方案总数。
  • 状态转移方程: dp[i] += dp[i - coin],表示在当前金额 i 上加上使用一个硬币 coin 之前可以构成的方案数。
  • 含义: 这是一个完全背包问题,每个硬币可以使用无限次,dp[i] 记录构成金额 i 的总方案数。

2. LeetCode 377: 组合总和 IV

  • dp 数组含义: dp[i] 表示用任意给定的数字构成目标数 i 的不同排列的总数。
  • 状态转移方程: dp[i] += dp[i - num],表示将一个数字 num 加到总和 i - num 的方案中,构成总和 i 的新方案。
  • 含义: 这是一个排列问题,数字的使用顺序不同会导致不同的组合,dp[i] 记录构成目标数 i 的不同排列数。

3. LeetCode 322: 零钱兑换

  • dp 数组含义: dp[i] 表示构成金额 i 需要的最少硬币数。
  • 状态转移方程: dp[i] = min(dp[i], dp[i - coin] + 1),表示通过当前硬币 coin 是否可以减少构成金额 i 的最少硬币数。
  • 含义: 这是一个最优解问题,类似最短路径问题dp[i] 记录的是构成金额 i 所需的最少硬币数。

4. LeetCode 279: 完全平方数

  • dp 数组含义: dp[i] 表示构成数字 i 所需的最少完全平方数的数量。
  • 状态转移方程: dp[i] = min(dp[i], dp[i - j*j] + 1),其中 j*j 是一个完全平方数,表示是否可以通过加上一个完全平方数 j*j 使得构成 i 的方案更优(更少的数量)。
  • 含义: 这是一个最优解问题dp[i] 记录的是使用最少的完全平方数构成数字 i 的最优方案。

5. LeetCode 139: 单词拆分

  • dp 数组含义: dp[i] 表示字符串的前 i 个字符是否可以拆分为字典中的单词组合。
  • 状态转移方程: dp[i] = dp[j] && (s[j:i] 在字典中),表示如果从 ji 这一段的字符串可以拆分为字典中的单词,并且前 j 个字符也可以成功拆分,那么前 i 个字符也可以拆分。
  • 含义: 这是一个可行性问题dp[i] 记录是否可以通过字典中的单词组合成功拆分字符串的前 i 个字符。

共同点总结

  1. dp[i] 的核心含义: 在所有这些题目中,dp[i] 都表示「达到目标 i 的一种最优解」或「达到目标 i 的方案总数」。有的题目求的是方案数(如 518、377),有的题目求的是最优解(如 322、279),有的题目求的是是否可行(如 139)。

  2. 递推性质: 在这些题目中,dp[i] 的值总是依赖于前面已计算的状态(如 dp[i-coin], dp[i-num], dp[i-j*j], dp[j] 等),体现了动态规划的最优子结构性质。通过对问题进行拆解,每一步都依赖之前的状态来进行更新。

  3. 类似背包问题的思想: 很多题目本质上可以类比为背包问题,特别是 518、322、279,它们都是求解「如何用最少或最多的方式(硬币、平方数、数列)来构成目标 i」。

  4. 状态转移方程: 这些题目的 dp 数组的更新都基于选择某个元素(如一个硬币、一个完全平方数或一个字母组合等),看是否能更新当前状态(最优解或方案总数)。

总结

在这些题目中,dp 数组都是通过逐步构造子问题的解,依赖于前面已经解决的子问题。dp 数组的核心思想是递推、优化、构成方案数或判断可行性,所有题目都以构成某个目标为最终目的。

4. 打家劫舍

LeetCode 198、213 和 337 这三道题的 dp 数组的共同点在于:它们都是求解「在相邻元素有约束条件时,如何选取最大收益的问题」。具体来说,它们涉及如何选择「不相邻的元素」来获取最大收益。它们的核心思想可以类比为一种「抢劫问题」,即如何从多个选择中选出一个最优解,且不能选择相邻的元素。

1. LeetCode 198: 打家劫舍 I

  • dp 数组含义: dp[i] 表示从第 0 个房子到第 i 个房子,能抢劫到的最大金额。
  • 状态转移方程: dp[i] = max(dp[i-1], dp[i-2] + nums[i]),表示对第 i 个房子来说,可以选择抢或者不抢。如果抢第 i 个房子,那么就不能抢第 i-1 个房子,因此要加上 dp[i-2],否则取 dp[i-1]
  • 含义: 这是一个经典的相邻约束问题,即选择非相邻房子来获取最大金额。

2. LeetCode 213: 打家劫舍 II

  • dp 数组含义: 与 198 类似,dp[i] 表示抢劫从第 0 个房子到第 i 个房子的最大金额,但这里的房子是环形排列
  • 状态转移方程: 基本思路与 198 相同,但由于房子呈环形,首尾房子不能同时抢劫,因此需要分成两种情况:
    • 不抢第一个房子,只考虑第 1 到第 n-1 个房子的情况。
    • 不抢最后一个房子,只考虑第 0 到第 n-2 个房子的情况。 最终结果取两种情况的最大值。
  • 含义: 这也是一个相邻约束问题,但增加了环形约束,导致边界条件有所不同。

3. LeetCode 337: 打家劫舍 III

  • dp 数组含义: dp[root] 是一个数组,dp[root][0] 表示不抢劫当前节点 root 时能获得的最大金额,dp[root][1] 表示抢劫当前节点时能获得的最大金额。
  • 状态转移方程:
    • 如果不抢劫当前节点,最大金额是子节点的抢劫与不抢劫的最大值之和:dp[root][0] = max(dp[left][0], dp[left][1]) + max(dp[right][0], dp[right][1])
    • 如果抢劫当前节点,最大金额是当前节点的值加上左右子节点不抢劫时的最大金额:dp[root][1] = root.val + dp[left][0] + dp[right][0]
  • 含义: 这是一个树形结构上的相邻约束问题,在树结构中,不能抢劫相邻的父子节点,求出最大金额。

共同点总结

  1. 非相邻元素的选择问题: 这几道题都围绕着「如何在不选择相邻元素的情况下获取最大收益」展开。dp[i]dp[root] 记录了在不同情况下的最大收益,即「抢当前元素」或「不抢当前元素」。

  2. 状态转移的递推逻辑: 它们的状态转移方程都是基于「选择某个元素并排除它的相邻元素」来计算的。例如:

    • 如果选择当前元素,则前一个元素不能选择;
    • 如果不选择当前元素,则前一个元素可以自由选择。
    • 对于树形结构,选择当前节点时,不能选择子节点。
  3. 最优子结构: 这些题目都有典型的最优子结构性质,即当前问题的最优解可以通过子问题的最优解来推导。在每一步都通过前面状态的最优值来计算当前状态的最优值。

总结

在这几道题中,dp 数组的含义都与「在相邻元素有约束的情况下求解最大收益」密切相关。无论是线性排列、环形排列还是树形结构,问题的核心都是通过动态规划方法处理相邻元素的选择限制,找到一种最优的选择方案来获取最大收益。

5. 股票问题

LeetCode 121、122、123、188、309 和 714 这些题目都是股票买卖问题,它们的 dp 数组的共同点在于:dp 数组用于表示在某个状态下最大利润的情况,而这些状态通常与买入、卖出、持有等动作相关。这些题目通过动态规划,逐步求解不同状态下的最优解,最终计算出最大收益。

共同点总结

  1. dp 数组表示某状态下的最大收益:

    • 这些题目中的 dp 数组或变量用于表示某一天的某个操作(持有股票、卖出股票等)所能获得的最大收益。通过状态转移方程来更新每个状态下的最优解。
  2. 状态转移方程基于买入、卖出、持有等操作:

    • 状态通常划分为「是否持有股票」或「完成几次交易」等,通过动态规划来递推每一天的最大收益。
  3. 限制条件不同:

    • 题目中限制条件可能有所不同,比如:
      • 只能进行一次交易(121)
      • 可以进行多次交易(122)
      • 最多进行两次交易(123)
      • 任意次数交易,但有冷冻期(309)
      • 任意次数交易,但有手续费(714)
      • 最多进行 k 次交易(188)
  4. 依赖前一天的状态:

    • 每一天的状态依赖于前一天的状态,形成一种递推关系,通过前一天的状态推导今天的最大利润。

1. LeetCode 121: 买卖股票的最佳时机

  • dp 含义: 在这道题中,只能进行一次交易。题目可以简化为用两个变量来表示 minPricemaxProfit
    • minPrice:到当前天为止的最低股票价格。
    • maxProfit:当前最大利润,等于当前价格减去之前的最低价格。

2. LeetCode 122: 买卖股票的最佳时机 II

  • dp 含义: 可以进行多次交易,每次交易后可以立即进行下一次交易。
    • dp[i][0] 表示第 i 天不持有股票的最大利润。
    • dp[i][1] 表示第 i 天持有股票的最大利润。
    • 状态转移:
      • dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i])(当天不持有股票,要么是前一天也不持有,要么是今天卖出了股票)。
      • dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i])(当天持有股票,要么是前一天持有,要么是今天买入了股票)。

3. LeetCode 123: 买卖股票的最佳时机 III

  • dp 含义: 最多进行两次交易。
    • dp[i][k][0] 表示第 i 天至多进行了 k 次交易且不持有股票时的最大利润。
    • dp[i][k][1] 表示第 i 天至多进行了 k 次交易且持有股票时的最大利润。
    • 状态转移:
      • dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])(不持有股票)。
      • dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])(持有股票,买入一次)。

4. LeetCode 188: 买卖股票的最佳时机 IV

  • dp 含义: 最多可以进行 k 次交易,题目与 123 类似,但可以有更多的交易次数。dp[i][k][0]dp[i][k][1] 的定义与 123 相同,表示当天最多进行了 k 次交易时的最大收益。

5. LeetCode 309: 最佳买卖股票时机含冷冻期

  • dp 含义: 有冷冻期的多次交易,交易后有一天冷冻期不能交易。
    • dp[i][0] 表示当天不持有股票的最大利润。
    • dp[i][1] 表示当天持有股票的最大利润。
    • dp[i][2] 表示当天处于冷冻期的最大利润。
    • 状态转移:
      • dp[i][0] = max(dp[i-1][0], dp[i-1][2])(不持有股票,昨天也不持有或处于冷冻期)。
      • dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i])(持有股票,昨天持有或今天买入)。
      • dp[i][2] = dp[i-1][1] + prices[i](今天卖出股票进入冷冻期)。

6. LeetCode 714: 买卖股票的最佳时机含手续费

  • dp 含义: 多次交易,但每次交易都需要手续费。
    • dp[i][0] 表示当天不持有股票的最大利润。
    • dp[i][1] 表示当天持有股票的最大利润。
    • 状态转移:
      • dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i] - fee)(不持有股票,卖出时需扣除手续费)。
      • dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i])(持有股票,买入时无手续费)。

总结

  1. dp 数组的共同含义: 在这些股票交易题中,dp[i][k][0]dp[i][k][1] 表示第 i 天至多交易 k 次时不持有或持有股票的最大利润。状态转移是基于「是否持有股票」以及「交易次数」。

  2. 操作选择: 在每一天,我们都有两个选择——「买入或持有」与「卖出或保持不持有」,通过这些选择的状态转移方程递推最大利润。

  3. 交易次数和冷冻期限制: 这些题目有各种限制条件(如交易次数、冷冻期、手续费等),但核心思想都是通过动态规划处理「每一天不同状态下的最大收益」。