动态规划有以下三类问题:
存在性问题
最值问题
计数问题
1.最值问题:
题目:有2元、5元和7元三种面值的硬币,如果想要拼成27元,怎么拼花费的硬币数量最少
暴力递归:
/*
return :构成num最少的硬币数量
param: num:需要拼成的面值
*/
public int getMinCoin(int num){
if(num == 0){
return 0;
}else if(num >0 && num <2){
/*
这里返回int的最大值是因为要求最少的硬币数,
所以如果在递归过程中出现了int的最大值,肯定是不满足要求的结果,也就是把不能拼成27元的情况略去了
*/
return Integer.MAX_VALUE;
}else if(num >=2 && num < 5){
return getMinCoin(num -2);
}else if(num >=5 &&num <7){
return Math.min(getMinCoin(num-2),getMinCoin(num-5));
}else {
//num >=7
return Math.min(getMinCoin(num-2),Math.min(getMinCoin(num-5),getMinCoin(num-7)));
}
}
DP
/*
coins:可用面值硬币的集合,比如在该案例中是2,5,7
num:需要凑齐的面值
*/
public int getMinCoin(int[] coins,int num){
//声明一个数组来记录已经计算过的结果
int[] result = new int[num-1];
//记录成功凑齐的最后结果init
result[0] = 0;
/*
for循环中最好使用++i
如果循环次数过大的话++i的性能会比i++好
原因:
不管是i++和++i,在for循环中只是自增作用,并没有把表达式赋给某一个变量
所以在for循环执行顺序的话是一样的
但是i++如果细分的话会比++i多一个赋值操作
所以会浪费一些性能
*/
for(int i = 1;i < num;++i){
result[i] = Integer.MAX_VALUE;
//遍历硬币集合
for(int j = 0;j<coins.length;++j){
/*
首先需要判断当前需要凑齐的面值是否大于硬币集合中的某一个值 --->num >= coins[j]
如果大于某一个值,则可能被该硬币凑齐
然后需要判断已经计算过的值result[i-coins[j]]是否可以被凑齐,也就是是否为Integer.MAX_VALUE
如果是,还需要判断result[i-coins[j]]+1 < result[i],也就是当前凑齐的方式是不是比之前所有的凑齐方式硬币数少
如果是,则更新result数组的值(这个值在某一个i值可能被多次更新,但是最后更新的值肯定是最少的硬币数)
*/
if(num >= coins[j] &&result[i-coins[j]] !=Integer.MAX_VALUE && result[i-coins[j]]+1 < result[i]){
result[i] = result[i-coins[j]]+1;
}
}
}
return result[num-1] == Integer.MAX_VALUE ? -1 : result[num-1];
}
2.计数问题:
题目:给定m行n列的网格,有一个人从左上角坐标(0,0)开始出发,问有多少种方式走到右下角(只能向下走或者向右走)
暴力递归
/*
return :到当前坐标共有多少种方式
param:坐标值
思路:
如果有x种方式走到(m-1,n),y种方式走到(m,n-1)
则通过x+y中方式走到(m,n)
*/
public int total(int i,int j){
if(i<0 || j<0){
return 0;
}else if(i==0 && j==0){
//这里必须是1,不然后面全都是0
return 1;
}else{
return total(i-1,j)+total(i,j-1);
}
}
DP
public int total(int m,int n){
int[][] result = new int[m][n];
result[0][0] = 1;
//计算顺序:按行计算数组
for(int i = 0;i < m,++i){
for(int j = 0; j < n;++j){
if(i == 0 || j == 0){
//处理边界
result[i][j] = 1;
}else{
result[i][j] = result[i-1][j] + result[i][j-1];
}
}
return result[m-1][n-1];
}
}
3.可行性分析
题目:有n块石头分别在x轴的0,1,2,... ,n-1位置
一只青蛙在石头0处
如果青蛙在第i块石头上,它最多往右跳ai个石头
问青蛙是否能够调到石头n-1处
DP
/*
return:是否可以跳到n-1的石头上
param: arr数组:
大小为n,也就是n块石头
数组中的值为处在第i个位置时,可以最多往右跳arr[i]个石头
*/
public boolean solution(int[] arr){
if(arr == null || arr.length == 0){
return false;
}
//石头数
int length = arr.length;
boolean[] result = new boolean[length];
// init 初始化第一个石头
result[0] = true;
//遍历石头
for(int i = 1;i<length;++i ){
//初始化是否能跳到这个石头
result[i] = false;
//遍历这个石头之前的所有石头
for(int j = 0;j<i;++j){
//如果能跳到i石头之前的某个石头j,而且在j石头上时最多能向右跳过的石头大于等于i与j之间的间距
//说明能够调到第i个石头上,并break跳出该循环
if(result[j] && arr[j] >= i-j){
result[i] = true;
break;
}
}
}
return result[length-1];
}