Table of Contents generated with DocToc
- 如何理解动态规划? - 九章算法的回答 - 知乎
- 如何理解动态规划? - zhen tan的回答 - 知乎
- 什么是动态规划(Dynamic Programming)?动态规划的意义是什么? - 阮行止的回答 - 知乎
基本思路:
- 判断是否是动态规划问题:
- 使用动态规划的问题一般都有一些特点可以遵循。例如,求最大/最小值;求可不可行;求方案总数。
- 大规模问题的答案可以由小规模问题的答案递推得到。即,状态可以转移,例如
f[i] = max{f[j] if j < i and …} + 1
,f[i][j] = f[i - 1][j] + f[i][j - 1]
- 子问题的求解思路除了规模之外,没有任何区别。子问题相互独立,即最优子结构。可以从子问题的最优结果推断出更大规模问题的最优结果
- 如果一个问题是要求出“所有的”方案和结果,则肯定不是使用动态规划
- 有递归终止条件
- 逐步剖析问题:
- 状态是什么
- 状态转移方程是什么:当已经知道
f(1)
~f(n - 1)
的值,然后想办法利用它们求得f(n)
- 状态的初始值是什么
- 问题要求的最后答案是什么
问题拆分成子目标:
- 建立状态转移方程
- 缓存并复用以往结果
- 按顺序从小往大算
确认遍历方式:
- 遍历的过程中,所需的状态必须是已经计算出来的
- 遍历的终点必须是存储结果的那个位置
明确问题(求什么) -> 明确状态(达成解时的状态时什么) -> 明确子问题(状态转移) -> 确认遍历方式(自底向上解决问题,遍历方式依赖于状态转移方向) -> 明确 base case(边界条件)
- 求最值
- 无后效性
- 重叠子问题
- 最优子结构
- 自底向上
股票系列问题
最基本思路:前一天的决策影响当天决策,经典的 DP 状态转移问题
- No121. 买卖股票的最佳时机
- No122. 买卖股票的最佳时机 II
- No123. 买卖股票的最佳时机 III
- No188. 买卖股票的最佳时机 IV
- No309. 最佳买卖股票时机含冷冻期
- No714. 买卖股票的最佳时机含手续费
- No53. 最大子序和
- No115. 不同的子序列
- No300. Longest Increasing Subsequence
- No354. Russian Doll Envelopes
- No368. 最大整除子集
- No646. Maximum Length of Pair Chain
- No673. 最长递增子序列的个数
- No718. 最长重复子数组
- No1027. 最长等差数列
- No1143. 最长公共子序列
- 面试题 17.08. 马戏团人塔
LeetCode 动态规划专题 6:0-1 背包问题在空间复杂度上的两个优化
- No139. 单词拆分
- No322. 零钱兑换 - 完全背包问题
- No377. 组合总和 Ⅳ
- No416. 分割等和子集
- No474. 一和零
- No494. 目标和
- No518. 零钱兑换 II - 完全背包问题
- No651. 4 键键盘
- No1049. 最后一块石头的重量 II - 01 背包问题
区间类动态规划是线性动态规划的扩展,它在分阶段地划分问题时,与阶段中元素出现的顺序和由前一阶段的哪些元素合并而来由很大的关系。令状态f(i, j)
表示将下标位置i
到j
的所有元素合并能获得的价值的最大值,那么f(i, j) = max(f(i, k) + f(k + 1, j) + cost)
,cost
为将这两组元素合并起来的代价。
对整个问题设最优值,枚举合并点,将问题分解为左右两个部分,最后合并两个部分的最优值得到原问题的最优值。
由于计算f(i, j)
的值时需要知道所有f(i, k)
和f(k + 1, j)
的值,而这两个中包含的元素的数量都小于f(i, j)
,所以我们以len = j - i + 1
作为 DP 的阶段。首先从小到大枚举len
,然后枚举i
的值,根据len
和i
用公式计算出j
的值,然后枚举在区间[i, j]
中枚举k
,时间复杂度为O(n^3)
通用公式:
// n 是区间总长度
for (let i = 1; i <= n; i += 1) {
dp[i][i] = 初始值
}
for (let len = 2; len <= n; len += 1) { // 从小到大枚举区间长度
for (let i = 1; i <= n - len + 1; i += 1) { // 枚举区间左端点
let j = i + len - 1 // 根据左端点和区间长度求区间右端点
if (j > n) break
dp[i][j] = 极限值
for(k = i; k < j; k += 1) { // 从区间 [i, j] 中任意位置截断
// w[i][j] 代表把两堆区间合并所需代价
// 求最大值则是 Math.max
dp[i][j] = Math.min(dp[i][j], dp[i][k] + dp[k + 1][j] + w[i][j])
}
}
}
return dp[1][n]
References:
-
No1167. 连接棒材的最低费用 - 此题不要求拼接顺序,因此其实是贪心算法问题。如果要求仅相邻才可拼接,则是经典的矩阵链乘法衍生问题