Reference:
- 算法-动态规划 Dynamic Programming–从菜鸟到老鸟
- 五大常用算法之二:动态规划算法
- Huahua Dynamic Programming (videos)
- Elements of Programming Interviews in Java, Dynamic Programming
What is DP?
Those who cannot remember the past are condemned to repeat it. — Dynamic Programing
The important fact to observe is that we have attempted to solve a maximization problem involving a particular value of $x$ and a particular value of $N$ by first solving the general problem involving an arbitrary value of $x$ and an arbitrary value of $N$. — R.E. Bellman, 1957
DP is a programming method.
Three Requirements:
- Optimal Substructure(最优子结构)- Can be solved optimally by decomposing it into subproblems adn then recursively find the optimal solutions to the subproblems.
- You should consider using DP whenever you have to make choices to arrive at the solution, specifically, when the solution relates to subproblems.
 
- Overlapping Subproblems(重叠子问题)- Subproblems are overlapped such that we can compute only once and store for future use.
- If subproblems do not overlap -> Divide and Conquer(they both combine the solutions of multiple smaller problems)
 
- No-after Effect(无后效性)- The optimal solution of a sub-problem will not change when it was used to solve a bigger problem optimally.
 
Applied to Two Kinds of Problems:
- Optimization(优化问题)或- Counting(计数问题)(min、max)
For some problems, DP will not work, e.g. the longest path problem.
Key Steps & Problem Types
Key Steps
- Find the recurrence(Big problem -> subproblems)- The most difficult part.
- Simple recursive solution incurs high complexity.- Has so many overlapped subproblems.
 
 
- Optimization (avoid repeated computation)- Top-down(recursion with- memoization)- Time complexity is clear / 时间复杂度比较清晰
- Space is usually cannot be optimized / 一般进行不了空间优化
 
- Bottom-up- Space can be readily optimized.
 
- Sometimes, recursion may out-perform a bottom-up DP solution, e.g., when the solution is found early or subproblems can be pruned through bounding.
 
Problem Types
- 50%: Sequence / 序列型- LIS / 单序列
- LCS / 双序列(common)
 
- 30%: Pascal Triangle / 矩阵坐标型
- 15%: Knapsack Variants (coin change, subset sum)
- 5%: Interval or others / 区间型及其他(optimal BST, matrix product, generate binary trees)- A bit difficult
 
Fibonacci Number (1D)
Recurrence: $F(n) = F(n - 1) + F(n - 2)$, where $F(0) = 0$ and $F(1) = 1$.
A function to compute $F(n)$ that recursively invokes itself has a time complexity that is exponential in $n$. This is because the recursive function computes some $F(i)$s repeatedly as shown in the figure below.

| 1 | // Recursion O(2^n) | 
Therefore, we can cache intermediate results and make the time complexity for computing the n-th Fibonacci number linear in $n$, but it is at the expense of $O(n)$ storage.
| 1 | // Top-down O(n) | 
Further, minimizing cache space is a recurring theme in DP. In a bottom-up fashion, we can reduce the space complexity of the cache.
| 1 | // Bottom-up O(n) it still needs O(n) space | 
Related LC Problems:
Unique Paths (2D)
How many possible unique paths are there?

Explanation: 62. Unique Paths (include obstacle version)
Related LC Problems:
- Amazon OA: 1102. Path With Maximum Minimum Value
Other Problems
Multiple States: dp[i][0], dp[i][1], dp[i][2]
Increasing Sum / Max / Prefix, Suffix:
Palindromic Problems:
The following contents are not organized!
The following contents are not organized!
The following contents are not organized!
Knapsack
Knapsack 是 NP-Complete 问题。
Note:没有任何贪心算法不能给最优解,比如拿 $\frac{v_i}{w_i}$
No Repetition
Input:
- v[i](i=0..n-1): Value
- w[i](i=0..n-1): Weight
- C: Max Weight
Note: Only one copy of each item at most.
Output: Max value sum.
Type: Optimization
问题:
- 怎么才能递归式地想问题呢?
- 怎么大问题化小问题?
- 怎么才是小问题?
**Note:**这里的 i 表示的是第几个,上面的是索引。
f(i, j): 如果只有 1 ~ i 个 item 可以选,并且此时的包重量为 j,你最多能拿走价值多少钱东西?
子问题:f(i-2, j)、f(i, j-10)、f(i-5, j-15)
选择:
- take it: f(i-1, j-w[i]) + v[i]
- leave it: f(i-1, j)
- 递归式:f(i, j)= max(f(i-1, j-w[i]) + v[i],f(i-1, j))
Recursion:
Note: 以下访问数组 w 和 v 时 i 都要减去 1。
| 1 | // O(2^n) | 
Top-down:
| 1 | // dp lookup | 
Bottom-up:

f(i, j) = max(f(i - 1, j), f(i - 1, j - w[i - 1]) + v[i - 1])
| 1 | // O(Cn) | 
Repetition
Input:
- v[i](i=0..n-1): Value
- w[i](i=0..n-1): Weight
- C: Max Weight
Note: Infinite copies of each item at most.
Output: Max value sum.
Type: Optimization
关键点:拿东西后这个东西还在,只是包的可用重量变小了。
Idea: f(i) 是当包的大小为i时,最多能拿走价值多少钱的东西。
递归式:f(i) = $\max_{k=1\ldots n}$(f(i - w[k]) + v[k])
Recursion:
| 1 | int ks(int[] v, int[] w, i) { | 
Top-down:
| 1 | int[] dp = new int[](n + 1); // and init with -1 | 
Bottom-up:

递归式:f(i) = $\max_{k=1\ldots n}$(f(i - w[k]) + v[k])
| 1 | int ks_bu(int[] v, int[] w, int C) { | 
Variants
有一个国家发现了 5 座金矿,每座金矿的黄金储量不同,需要参与挖掘的工人数也不同。参与挖矿工人的总数是 10 人。每座金矿要么全挖,要么不挖,不能派出一半人挖取一半金矿。要求用程序求解出,要想得到尽可能多的黄金,应该选择挖取哪几座金矿?
332. Coin Change
Bottom-up:
| 1 | public int coinChange(int[] coins, int amount) { | 
你好,我是分界线
Two methods:
- Top-down (Memo) => Extra memory
- Bottom-up => Better
Key steps:
- Recursion
- Store (Memoize intermediate results)
- Bottom-up (optional)
钢条切割


对于 $r_n(n \geq 1)$,我们可以用更短的钢条的最优切割收益来描述:
$$r_n = \max(p_n, r_1 + r_{n-1}, r_2 + r_{n-2}, \ldots, r_{n-1} + r_1)$$
For example, when $n = 3$ we have as follows:
$$r_3 = \max(p_3, r_1 + r_2, r_2 + r_1)$$
第一个参数 $p_n$ 对应不切割,直接求出长度为 $n$ 的方案,其他 $n-1$ 个参数对应了 $n-1$ 种切割方案。
关于子问题的最优解,并在所有可能的两端切割方案重选取组合收益最大者,构成原问题的最优解。我们称钢条切割问题满足最优子结构(optimal substructure)性质:
- 问题的最优解由相关子问题的最优解组合而成,而这些子问题可以独立求解。
钢条切割问题还存在一种相似的但更为简单但递归求解方法:我们将钢条从左边切割下长度为 $i$ 的一段,只对右边剩下的长度为 $n-i$ 的一段继续进行切割(递归求解),对左边一段不再进行切割。因此,上面的公司可以简化为:
$$r_n = \max_{1 \leq i \leq n}(p_i + r_{n-i})$$
For example, when $n = 3$ we have as follows:
$$r_3 = \max_{1 \leq i \leq 3}(p_1 + r_2, p_2 + r_1, p_3 + r_0)$$
| 1 | 0 1 2 3 4 5 6 7 8 9 | 
Recursive version:
| 1 | public static int cut(int[] p, int n) { // n is the length | 
Memo version:
| 1 | public static int cutMemo(int[] p) { | 
这道钢条切割问题的经典之处在于自底向上的动态规划问题的处理,理解了这个也就理解了动态规划的精髓。
Bottom-up dp version:
| 1 | public static int bottom_up_cut(int[] p) { | 
0-1 Knapsack
Naive Recursive Solution:

| 1 | int[] v; | 
