【题目】
给定数组arr,arr中所有的值都为正数且不重复。每个值代表一种面值的货币,每种面值的货币可以使用任意张,再给定一个整数aim代表要找的钱数,求组成aim的最少货币数。
【举例】
arr=[5,2,3],aim=20
4张5块可以组成20,其他的方案都要使用更多张的货币,所以返回4.
arr=[5,2,3],aim=0
不用任何货币就可以组成0元,所以返回0.
arr=[3,5],aim=2
根本无法组成2元,钱不能找开的情况下默认返回-1.
【代码】
public static void main(String[] args) {
int[] m={5,2,3};
//方法1
System.out.println(minCoins1(m,20));//4
System.out.println(minCoins1(m,0));//0
System.out.println(minCoins1(new int[]{5,3},2));//-1
//方法2
System.out.println(minCoins2(m,20));//4
System.out.println(minCoins2(m,0));//0
System.out.println(minCoins2(new int[]{5,3},2));//-1
}
//换钱的最小货币数
//方法1,经典动态规划,复杂度都为O(N×aim)
public static int minCoins1(int[] arr,int aim){
if(arr==null||arr.length==0||aim<0){
return -1;
}
int n=arr.length;
int max=Integer.MAX_VALUE;
int[][] dp=new int[n][aim+1];
//dp[i][j]的意义:任意使用arr[0...n]组成j所需的最小张数
//赋值dp的第一行,表示只能使用arr[0]的情况下,找某个钱数的最小张数
//如arr[0]=2,能找开2,4,6,8... 所以dp[0][2]=1,dp[0][4]=2...其他为max
for(int j=1;j<=aim;j++){
dp[0][j]=max;
if(j-arr[0]>=0 && dp[0][j-arr[0]]!=max){
//aim>arr[0],且j是arr[0]的倍数
dp[0][j]=dp[0][j-arr[0]]+1;
}
}
int left=0;
for(int i=1;i<n;i++){
for(int j=1;j<=aim;j++){
left=max;
if(j-arr[i]>=0&&dp[i][j-arr[i]]!=max){
left=dp[i][j-arr[i]]+1;
}
dp[i][j]=Math.min(left, dp[i-1][j]);
//一般情况,dp[i][j]=min{ dp[i-1][j], dp[i][j-arr[i]]+1 }
}
}
return dp[n-1][aim]!=max?dp[n-1][aim]:-1;
}
//方法2,空间压缩,时间复杂度为O(N×aim),空间复杂度为O(aim)
public static int minCoins2(int[] arr,int aim){
if(arr==null||arr.length==0||aim<0){
return -1;
}
int n=arr.length;
int max=Integer.MAX_VALUE;
int[] dp=new int[aim+1];
for(int j=1;j<=aim;j++){
dp[j]=max;
if(j-arr[0]>=0 && dp[j-arr[0]]!=max){
dp[j]=dp[j-arr[0]]+1;
}
}
int left=0;
for(int i=1;i<n;i++){
for(int j=1;j<=aim;j++){
left=max;
if(j-arr[i]>=0 && dp[j-arr[i]]!=max){
left=dp[j-arr[i]]+1;
}
dp[j]=Math.min(dp[j], left);
}
}
return dp[aim]!=max?dp[aim]:-1;
}