动态规划
动态规划: 通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。通常许多子问题非常相似,为此动态规划法试图仅仅解决每个子问题一次,从而减少计算量:一旦某个给定子问题的解已经算出,则将其记忆化存储,以便下次需要同一个子问题解之时直接查表。这种做法在重复子问题的数目关于输入的规模呈指数增长时特别有用。
动态规划常规解题套路:
1、使用普通方式尝试解决问题,如使用递归;
2、将普通方式改进为记忆搜索,即,将递归时的值存进数组,如遇到重复的子问题时,直接取值即可;
3、将记忆化搜索改进为严格表格式,即根据递归式的表达式和子问题数值在多维数组中的位置取值,这一步时,可以不用理会原题意得解题套路,只关心在多维表中的取值方式。
例题
一个数组中,数组元素分别代表一个硬币及硬币的金额,给定一个金额数,求达到给定金额数的最少需要多少个硬币
数组:arr = [1,2,3,4,5,6]
解题
解题思路: 每遍历到一个硬币(数组元素)均可选择要与不要这个硬币这种情况,当已经达到金额数时,均返回0,表示已经不需要再继续拿金币了;当已经超过金额数时,返回-1,表示到此位置的拿取硬币的方式已经是错误的;当所有硬币均拿完了,还未达到金额数,返回-1,表示拿取硬币的方式已经时错误的。
1、使用暴力递归解法
//解法一: 暴力递归
public void function1(int[] arr){
int all = proccess(arr,0,10);
System.out.println("最小硬币数是:"+all);
}
public int proccess(int[] arr, int index,int aim){
if(index == arr.length) return -1;
if(aim < 0) return -1;
if(aim == 0) return 0;
int getUp = proccess(arr,index+1,aim);
int get = proccess(arr,index+1,aim-arr[index]);
if(getUp == -1 && get == -1){
return -1;
}else {
if(getUp == -1) return get+1;
if(get == -1) return getUp;
}
return Math.min(getUp,get);
}
2、记忆搜索
每次递归解决子问题时,将子问题的数值在数组中存储一下
//解法二: 记忆搜索 --简单傻瓜式存储值
public void function2(int[] arr){
int aim = 10;
int[][] dp = new int[arr.length+1][aim+1];
for(int i=0; i<arr.length+1; i++){
for (int j=0; j<= aim; j++) dp[i][j] = -2; //表示未算过的位置
}
int all = proccess2(arr,0,aim,dp);
System.out.println("最小硬币数是:"+all);
}
public int proccess2(int[] arr,int index,int aim,int[][] dp){
if(aim < 0){
return -1;
}
if(dp[index][aim] != -2) {
return dp[index][aim];
}
if(aim == 0) {
dp[index][aim] = 0;
}
else if(index == arr.length){
dp[index][aim]= -1;
}
else {
int getUp = proccess2(arr,index+1,aim,dp);
int get = proccess2(arr,index+1,aim-arr[index],dp);
if(getUp == -1 && get == -1) dp[index][aim] = -1;
else {
if(getUp == -1){
dp[index][aim] = get+1;
}else if(get == -1){
dp[index][aim] = getUp;
}else {
dp[index][aim] = Math.min(getUp,get+1);
}
}
}
return dp[index][aim];
}
3、严格表结构解法
根据要与不要的时的递归表达式,确定该位置的取值,如图:
以上代码可得:在[3,2]的位置时:
要这个2位置这个硬币时,子问题的取值方式为:proccess(arr,index+1,aim);
不要这个2位置这个硬币时,子问题的取值方式为:proccess(arr,index+1,aim-arr[index]);
可得,该位置的取值依赖子问题的位置为:[3,6]和[3,10]
代码:
//严格表结构法
public int function3(int[] arr){
int lenght = arr.length;
int aim = 10;
int[][] dp = new int[lenght+1][aim+1];
//临界条件填值 -- if条件
for(int i=0; i<=lenght; i++) dp[i][0] = 0;
for(int j=0; j<=aim; j++) dp[lenght][j] = -1;
//根据递归的算术表达式获取值
for(int index = lenght-1; index>=0; index--){
for(int rest = 1; rest <=aim; rest++){
int getUp = dp[index+1][rest];
int get = -1;
if(rest-arr[index] >=0) get = dp[index+1][rest-arr[index]];
if(getUp ==-1 && get == -1) dp[index][rest] = -1;
else {
if(getUp == -1) dp[index][rest] = get+1;
else if (get == -1) dp[index][rest] = getUp;
else dp[index][rest] = Math.min(get,getUp);
}
}
}
System.out.println("最小硬币数是:"+dp[0][aim]);
return dp[0][aim];
}
注意:无论是记忆搜索还是严格表结构,均需注意临界值!!!