动态规划(一)
简介
动态规划,Dynamic Programming,简称DP。
特点
- 最优子结构
- 重叠子问题
解题一般思路
- 分解子问题:子问题与原问题相同/相似,只是缩小了规模,“大而化小,小而化了”。
- 确定状态:子问题各变量的一组取值
- 确定边界:初始状态,或者边界条件的取值
- 确定状态转移方程:递推的公式
关键是确定状态转移方程,在什么条件下进行什么状态的转换。
DP vs. 递归
- 递归:自上而下,就会有重复的子问题
- DP:自下而上,用空间换时间
🌰
- 最长公共子序列LCS
- 最长上升子序列
题目
首先从最简单的开始,通过斐波那契数列来感受重叠子问题。
斐波那契数列
F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
给定 N
,计算 F(N)
。
显然,这个问题用递归求解很简单。
1 | int fib(int N) { |
附上力扣的题目链接https://leetcode-cn.com/problems/fibonacci-number/
假设N=6
,画出递归树,可以发现有很多重复的子问题,也就是在求解的过程中进行了重复计算,增加了时间的开销。
我们把重复计算的地方存下来,“用空间换时间”,避免重复的计算。
1 | int fib(int n) { |
附上力扣题目链接https://leetcode-cn.com/problems/fei-bo-na-qi-shu-lie-lcof/
70. 爬楼梯
到达第n阶楼梯的方法数量 = 到达第n-1阶楼梯的方法总数 + 到达第n-2阶楼梯的方法总数 解释:最后一步可能走一个阶梯,也可能走两个阶梯 其实是一个斐波那契数列
- 状态
d[i]
表示达到第i
阶的方法总数
边界:
d[0] = 1 实际上无这种情况,为递推定义的 d[1] = 1 n>=2时,进行状态转移
状态转移方程:
d[n] = d[n - 1] + d[n - 2]
解题代码:
1 | int climbStairs(int n){ |
746. 使用最小花费爬楼梯
- 分析过程
对于每个阶梯,都有选和不选,两种情况。
假设求d[9],选择第9个阶梯,那么有两种情况:一是选择第7个阶梯,二是选择第8个阶梯。
...依次做下去。
- 状态
d[i]
代表到达i
阶的最小花费,i=0
和i=1
的情况下特别处理
边界
n<2的时候,取min(cost[0], cost[1]); d[0] = cost[0]; d[1] = cost[1];
结果
求的是到达n的前1阶或前2阶的最小值,即min(d[costSize - 2], d[costSize - 1]);
- 状态方程
1 | d[i] = min(d[i - 2] + cost[i], d[i - 1] + cost[i]); // 当 i >=2 时 |
- 代码
1 | // |
经典问题的状态转移方程
力扣上的一个关于DP的总结
https://leetcode.com/discuss/general-discussion/458695/dynamic-programming-patterns#Decision-Making