1 动态规划简介
动态规划在查找有很多重叠子问题的情况的最优解时有效。它将问题重新组合成子问题。为了避免多次解决这些子问题,它们的结果都逐渐被计算并被保存,从简单的问题直到整个问题都被解决。因此,动态规划保存递归时的结果,因而不会在解决同样的问题时花费时间。
动态规划只能应用于有最优子结构的问题。最优子结构的意思是局部最优解能决定全局最优解(对有些问题这个要求并不能完全满足,故有时需要引入一定的近似)。简单地说,问题能够分解成子问题来解决。
2 动态规划题目特点
2.1 计数
- 有多少种方式走到右下角(输出所有路径,用递归)
- 有多少种方法选出K个数使得和为SUM
2.2 求最大最小值
- 从左上角走到右下角路径的最大数字和
- 最长上升子序列长度
- 用最少的硬币组合付钱
2.3 求存在性
- 取石子游戏,先后是否得胜
- 能不能选出k个数使得和是Sum
例题:有数量不限,面值为[2, 5, 7] 的硬币,买27元的书,使用硬币一次付清不找零最少要几个硬币?
3 动态规划组成部分
3.1 确定状态
解动态规划的时候,需要开一个数组,数组的每个元素f[i] 或者 f[i][j] 代表什么,类似于解数学题中x, y, z 各代表什么。
确定状态需要两个意识:
-
最后一步
虽然目前并不知道最优策略,但最优策略肯定是K枚硬币a_1, a_2+…a_k = 27.所以一定有一枚最后的硬币: a_k.
除掉最后这枚硬币,前面加起来是27- a_k
关键点一:
我们并不关心前面k-1枚硬币怎么拼出27- a_k的,但可以确定前面的硬币拼出了27- a_k
关键点二:
因为是最优策略,所以拼出27-a_k 的硬币数一定要最少,否则就不是最优。 -
子问题
所以就先求,最少用多少枚硬币可以拼出27- a_k.
如此便将原问题转化为了规模更小的子问题
为简化定义,我们设状态f(X) = 最少用多少枚硬币拼出X.
目前还不知道a_k 是多少,但他只能是 2,5,7中的一个.
如果a_k 是2,f(27)应该是f(27-2) + 1
如果a_k是5,f(27)应该是f(27-5) + 1
如果a_k是7,f(27)应该是f(27-7) + 1
所以f(27) = Min{ f(27-2)+1, f(27-5)+1, f(27-7)+1 }
对比递归解法
int f(int X) {
if(X==0) return 0;
int res = MAX_VALUE;
if(X>=2){
res = Math.min(f(X-2)+1, res);
}
if(X>=5){
res =Math.min(f(X-5)+1, res);
}
if(X>=7){
res = Math.min(f(X-7)+1, res);
}
return res;
}
递归解法的问题:
可以看到,光 f(20) 就重复算了三次,而每算一次 f(20) 还要再算下面的。造成了运行时间的浪费。
如何避免重复计算,效率低下的问题?
动态规划:将计算结果保存下来,并改变计算顺序。
3.2 转移方程
因为动态规划一般需要开一个数组,接下来用 [ ] 来代替 ( )
设状态 f [X] = 最少用多少枚硬币拼出X.
对于任意X, f [X] = Min{ f [X-2]+1, f [X-5]+1, f [X-7]+1 }
3.3 写程序之前–初始条件和边界情况
对于转移方程 f [X] = Min{ f [X-2]+1, f [X-5]+1, f [X-7]+1 }, 有两个问题
- X-2, X-5, X-7小于零怎么办?
- 什么时候停下来?
-
对于问题1, 如果不能拼出Y, 就定义f [Y] = 正无穷, 如 f [-1] = f [-2] = 正无穷. 这样的话, f [1] = MIn{ f [-1] +1, f [-4] +1, f[-6]+1 } = 正无穷,表示拼不出来 1
-
初始条件 :f [0] = 0.
初始条件就是用转移方程算不出来,但又需要补充的定义。 -
边界条件: 不让数组越界
-
计算顺序 ?:对这道题,从小到大算,这样可以避免重复计算,即当算f [X] 时,f [X-2],f [X-5], f [X-7] 都已经得到结果了。每一步尝试三种硬币,一共27步,与递归算法相比,没有任何重复计算。算法时间复杂度:273 O(NM)
例题:
Leetcode 322. Coin Change
You are given coins of different denominations and a total amount of money amount. Write a function to compute the fewest number of coins that you need to make up that amount. If that amount of money cannot be made up by any combination of the coins, return -1.
Example 1:
Input: coins = [1, 2, 5], amount = 11
Output: 3
Explanation: 11 = 5 + 5 + 1
Example 2:
Input: coins = [2], amount = 3
Output: -1
Note:
You may assume that you have an infinite number of each kind of coin.
class Solution {
public int coinChange(int[] coins, int amount) {
int[] f = new int[amount+1];
int n = coins.length;
// initialization
f[0] = 0;
for(int i=1; i<=amount; ++i){
f[i] = Integer.MAX_VALUE;
// last coin
for(int j=0; j<n; ++j){
if(i>=coins[j] && f[i-coins[j]]!=Integer.MAX_VALUE){
f[i] = Math.min(f[i-coins[j]]+1, f[i]);
}
}
}
if(f[amount]==Integer.MAX_VALUE) {
f[amount] = -1;
}
return f[amount];
}
}