动态规划(Dynamic Programming)
1.动态规划题目特点
- 计数:
- 有多少种方法走到右下角
- 有多少种方法选出k个数使得和是Sum
- 求最大最小值
- 从左上角到右下角路径的最大数字和
- 最长上升子序列的长度
- 求存在性
- 取石子游戏,先手是否必胜
- 能不能选出k个数字使得和是sum
2.例题
1.leetcode-322. 零钱兑换
- 求最大最小值
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
示例 1:
输入: coins = [1, 2, 5], amount = 11
输出: 3
解释: 11 = 5 + 5 + 1
示例 2:
输入: coins = [2], amount = 3
输出: -1
以 coin = [2,5,7], amount = 27为例;
3.动态规划组成部分
3. 1确定状态
-
解决动态规划的时候需要开一个数组,数组的每个元素f[i]或者f[i][j]代表什么.(类似于数学题中的x,y,z);
-
确定状态需要两个意识:
- 最后一步:虽然不知道最优策略是什么,但一定是k枚硬币的序列,所以一定有最后一枚硬币ak;[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7aY3FpLO-1585142147154)(/home/guomingzhe/图片/2020-03-25 18-05-31 的屏幕截图.png)]
- 子问题:最少用多少枚硬币可以拼出27-ak,将原来的问题转换为了一个规模更小的子问题;
求解:f(27) = min(f(27-2)+1, f(27-5)+1, f(27-7)+1)
递归解法:
// 超时不通过
public class A0322_CoinsChange {
int res = Integer.MAX_VALUE;
public int coinChange(int[] coins, int amount) {
if(coins.length == 0){
return -1;
}
findWay(coins,amount,0);
// 如果没有任何一种硬币组合能组成总金额,返回 -1。
if(res == Integer.MAX_VALUE){
return -1;
}
return res;
}
public void findWay(int[] coins,int amount,int count){
if(amount < 0){
return;
}
if(amount == 0){
res = Math.min(res,count);
}
for(int i = 0;i < coins.length;i++){
findWay(coins,amount-coins[i],count+1);
}
}
}
递归方法中,若数值大,由于重复计算,效率底下,导致运算超时;
3.2 转移方程
- 设状态f[x]=最少用多少枚硬币拼出X
- 对于任意X, f[x] = min(f(27-2)+1, f(27-5)+1, f(27-7)+1)
3.3 初始条件和边界情况
- f[x] = min(f(27-2)+1, f(27-5)+1, f(27-7)+1)
- x-2,x-5,x-7小于零?什么时间停下来;
- 如果无解,定义f[y] = 正无穷
- 初始条件f[0] = 0 即用转移方程算不出来的,但是可以手动算出来;
3.4计算顺序
- 拼出X所需要的最少硬币数
- 初始条件:f[0] = [0]
- 依次计算:f[1],f[2],…,f[27]
- 当计算到f[x]时,f[x-2],f[x-5],f[x-7],已经取得结果了
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nUJwtRkG-1585142147157)(/home/guomingzhe/图片/2020-03-25 18-21-07 的屏幕截图.png)]
每一步尝试三种硬币,一共是27步;
与递归相比,没有任何的重复计算;
算法的时间复杂度为O(n):27*3, n; <<<递归的时间复杂度;
4.总结
- 求最值型动态规划
- 动态规划组成部分:
- 1.确定状态
- 最后一步(最优策略中最后一枚硬币ak);
- 化成子问题;
- 2.转移方程
- f[x] = min(f(27-2)+1, f(27-5)+1, f(27-7)+1);
- 3.初始条件和边界情况
- f[0] = 0,如果不能拼出Y,f[Y] = -1;
- 4.计算顺序
- f[1],f[2],…,f[27];
- 1.确定状态
- 减少冗余,加速计算
public int coinChange(int[] coins, int amount) {
// 自底向上的动态规划
if(coins.length == 0){
return -1;
}
// memo[n]的值: 表示的凑成总金额为n所需的最少的硬币个数
int[] memo = new int[amount+1];
memo[0] = 0;
for(int i = 1; i <= amount;i++){
int min = Integer.MAX_VALUE;
for(int j = 0;j < coins.length;j++){
if(i - coins[j] >= 0 && memo[i-coins[j]] < min){
min = memo[i-coins[j]] + 1;
}
}
// memo[i] = (min == Integer.MAX_VALUE ? Integer.MAX_VALUE : min);
memo[i] = min;
}
return memo[amount] == Integer.MAX_VALUE ? -1 : memo[amount];
}
}