您的位置:首页 > 其它

动态规划5:找零钱问题

2017-07-18 10:35 155 查看
题目:

有数组penny,penny中所有的值都为正数且不重复。每个值代表一种面值的货币,每种面值的货币可以使用任意张,再给定一个整数aim(小于等于1000)代表要找的钱数,求换钱有多少种方法。给定数组penny及它的大小(小于等于50),同时给定一个整数aim,请返回有多少种方法可以凑成aim。

样例:[1,2,4],3,3  返回:2(即1 1 1,1 2)

思路:


[aim+1]记录子问题结果,dp[i][j]表示钱为j,且货币为penny[0...i]种时,共有的找钱方式。

目标值从aim=0开始



将这个问题分解成为N行,aim+1列的矩阵dp
[aim+1],矩阵中每个值dp[i][j]:arr[0~i]来拼凑出目标值j的方案数目,于是将复杂问题转化成为了N*(aim+1)个子问题。

初始条件,可知dp[i][0]=1,对于第一行dp[0][j],表示只用arr[0]这种货币进行拼凑,那么只有该货币的整数倍的钱数才能拼凑成功。所以dp[0][j]=1(j是arr[0]的倍数),否则dp[0][j]=0。

对于任意的一个值dp[i][j]表示使用arr[0~i]的元素来拼凑出目标值为j的方案数目,总结规律可以发现:dp[i][j]=dp[i-1][j]+dp[i-1][j-arr[i]]+dp[i-1][j-arr[i]*2]+……即arr[i]选0,1,2,...个时的情况

对动态规划矩阵中的计算顺序路径作出规定,只有按行从上到下,每行从左到右进行计算,才能保证在计算dp[i][j]时dp[i-1][
ced3
j]+dp[i-1][j-arr[i]]+dp[i-1][j-arr[i]*2]+……都已经计算出来,否则就无法解决问题。因此在本问题中,在计算出了第一行第一列的结果dp[i][j]之后,对之后的dp[i][j]不能随意计算,必须按照从第1行第2列开始逐行逐列地进行计算,可以使用一个双层遍历来进行计算所有值。当所有dp[i][j]计算完成后,矩阵右下角的元素值dp[n-1][aim]就是所求的结果值。

总结:

对于任意一个dp[i][j]在求解的过程中,需要对i-1行中的若干个结点值进行求和运算,即要通过一个循环函数按照for(int k=0,k*arr[i]<j,k++)对该列进行遍历和枚举,于是对于每个结点的枚举的时间复杂度是O(aim),由于要对n*(aim+1)个结点进行枚举,因此总的时间复杂度是O(n*aim^2);

 

例如对于上面dp[i][j]=dp[i-1][j]+dp[i-1][j-arr[i]]+dp[i-1][j-arr[i]*2]+……的计算过程可以进行进一步的简化,分析发现,例如图中所示,dp[i-1][j-arr[i]]+dp[i-1][j-arr[i]*2]的计算结果就是dp[i][j-arr[i]]的计算结果,于是dp[i][j]= dp[i-1][j]+
dp[i][j-arr[i]],同时由于这2项都出现在dp[i][j]的计算顺序之前,因此可以直接拿来使用,因此对于每一项dp[i][j]在计算时省去了遍历枚举的过程,于是每一个dp[i][j]的计算时间复杂度降低为O(1),因此总的时间复杂度降低为O(n*aim)。这个规律是因为计算时严格按照逐行逐列路径进行计算时才有的,因此使用动态规划时按照固定路径进行计算可以为进一步的优化带来可能。
public class Exchange {
public int countWays(int[] penny, int n, int aim) {
// write code here
int[][] dp=new int
[aim+1];
//初始状态
for(int i=0;i<n;i++) dp[i][0]=1;//aim=0时
for(int j=1;j<=aim;j++){//第一行
int i=penny[0];
if(j%i==0) dp[0][j]=1;
else dp[0][j]=0;
}

//从上到下 从左到右
for(int i=1;i<n;i++){
for(int j=1;j<=aim;j++){
if(j>=penny[i]){//在前i-1项里面拼凑j,和在前i项里拼凑j-penny[i]--默认已经选择一个i
dp[i][j]=dp[i-1][j]+dp[i][j-penny[i]];
}else{
dp[i][j]=dp[i-1][j];
}
}
}

return dp[n-1][aim];
}
}


用一维数组:每一列看成一维

public class Exchange {
public int countWays(int[] penny, int n, int aim) {
// write code here
int[] dp=new int[aim+1];//表示每种钱数j有多少种拼凑情况

for(int j=0;j<=aim;j++){//初始
if(j%penny[0]==0) dp[j]=1;
else dp[j]=0;
}

for(int i=1;i<n;i++){
for(int j=1;j<=aim;j++){
if(j>=penny[i]) dp[j]=dp[j-penny[i]]+dp[j];
}
}
return dp[aim];
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: