动态规划特辑-01
一、动态规划的题目特点
1、计数
- 有多少种方法走到右下角
- 有多少种方法选出k个数使得和为sum
2、求最大值最小值
- 从左上角走到右下角路径的最大数字和
- 最长上升子序列长度
3、求存在性
- 取石子游戏,先手是否必胜
- 能不能取出k个数使得和是sum
二、求最值型动态规划——案例讲解解题步骤
1、问题描述---求最大最小值的动态规划
2、动态规划组成部分一:确定状态
- 简单的说,解决动态规划问题的时候需要开一个数组,不管是几维的数组,我们都要明白数组的每个元素f[i]或者f[i][j]都代表什么。
- 确定状态需要两个意识:
①最后一步
目的是通过最后一步,把问题转化成子问题。
②子问题
③递归解法
递归解法的问题
f(20重复了3次,递归重复了好多次,等到程序的数量特别庞大的时候,就会造成问题。
3、动态规划组成部分二:转移方程
4、动态规划组成部分三:初始条件和边界情况
初始条件就是把最小的情况定义下来,边界情况就是不要数组越界。
5、动态规划组成部分四:计算顺序
从大到小还是从小到大去计算
顺序演示:
正无穷代表拼不出来
最后,可以算出f[27]要用5枚硬币。
计算次数
由于没有重复的计算,相比于递归,动态规划的速度很快。
6、小结
7、题目练习——lintcode-669.换硬币
①问题描述
②代码解决
需要注意的点:
- 数组开辟的大小
- 初始化f[0]
- f[i]的处理,Integer的最大值
- 转移方程
public class Solution {
/**
* @param coins: a list of integer
* @param amount: a total amount of money amount
* @return: the fewest number of coins that you need to make up
*/
public int coinChange(int[] coins, int amount) {
//开多大的数组呢?amount就是总量
//如果要用到n,我们就要开辟数组[n+1],因为下标从0开始
//如果要用到n-1,我们就开辟数组[n]
int[] f = new int[amount + 1];
int n = coins.length;//number of kinds of coins
//initialization
f[0] = 0;
int i,j;
//f[1] f[2] ... f[27]
for (i = 1; i <= amount; ++i){
f[i] = Integer.MAX_VALUE;
//last coinA[j]
//f[i] = min{f[i-A[0]]+1,...,f[i-A[n-1]]+1]}
for (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];
}
}
三、计数型动态规划——机器人路径问题
1、题目描述
2、步骤一:确定状态
①最后一步
②子问题
3、步骤二:转移方程
4、步骤三:初始条件和边界情况
5、步骤四:计算顺序
如何确定计算顺序呢?根据转移方程来,在算右下角的格子时候,左面的格子刚刚算得到,上面的格子已经得到。
6、题目练习——lintcode-114.不同的路径
①问题描述
②代码解决
- 这里把初始化放到了for循环里面,为什么不在外面写呢?因为for循环会把所有格子都遍历一遍,所以直接写到里面就好了。
public class Solution {
/**
* @param m: positive integer (1 <= m <= 100)
* @param n: positive integer (1 <= n <= 100)
* @return: An integer
*/
public int uniquePaths(int m, int n) {
//注意此处为什么之开辟m到n,因为最终的结果只用到[m-1,n-1]
int[][] f = new int[m][n];
int i,j;
for (i=0;i<m;i++){//row: top to bottom
for(j=0;j<n;j++){//column: left to right
if(i==0 || j==0){
f[i][j] = 1;
}else{
f[i][j] = f[i-1][j] + f[i][j-1];
}
}
}
return f[m-1][n-1];
}
}
四、存在型可行性型动态规划——Jump Game
1、题目描述
2、步骤一:确定状态
①最后一步
通过最后一步来确定子问题
②子问题
3、步骤二:转移方程
4、步骤三:初始条件和边界情况
5、步骤四:计算顺序
6、题目练习——lintcode-116.跳跃游戏
①问题描述
②代码解决
public class Solution {
/**
* @param A: A list of integers
* @return: A boolean
*/
public boolean canJump(int[] A) {
int n = A.length;
boolean[] f = new boolean[n];
f[0] = true;//initialization
for (int j=1;j < n;j++){
f[j] = false; //假设不能通过
//previous stone i
//last jump is from i to j
for (int i=0; i < j; i++){
if(f[i] && i + A[i] >= j){
f[j] = true;
break;
}
}
}
return f[n-1];
}
}