每日一題之動歸-換錢的最少次數(一)
題目:
給定陣列arr,arr中所有的值都為正數且不重複。每個值代表一種面值的貨幣,每種面值的貨幣可以使用任意張,再給定一個整數aim代表要找的錢數,求組成aim的最少貨幣數。
舉個例子
arr[5,2,3] ,aim=20
4張5元可以組成20,並且是最小的,所以返回4
arr[5,2,3],aim=0。
不用任何貨幣就可以組成0元,這裡返回0.
arr[5,2,3],ami=4
這裡無法組成返回-1
思路:這裡我們採用經典的動態規劃的方法做,時間複雜度可以在O(n^2)。
經典動態規劃的放法。如果arr的長度N,生成的行數為N,列數為aim+1的動態規劃表dp。dp[i][j]的含義是,在可以任意使用arr[0..i]貨幣的情況下,
組成j所需的最小張數。根據這個定義,dp[i][j]有如下計算方法
1.dp[0..N-1][0]的值表示找的錢數為0的時候需要的最少張數,錢數為0時,完全不需要任何貨幣所以全部設定為0.
2.dp[0][0..aim]表示只能使用arr[0]貨幣的情況下找某個簽署的最小張書。比如,arr[0]=2,那麼能找開的錢數為2,4,6,8,…所以令dp[0][2]=1,dp[0][4]=2,…,
其他位置都是找不開的,其餘一律設為32位整數最大值,我們把這個值記為max。
3.剩下的位置以此從左到右,再從上到下計算。
情況有如下:
1.不利用當前i的直接,即dp[i][j]=dp[i-1][j]的值
2.只使用1張當前貨幣arr[i]情況下最少的,即dp[i-1][j-arr[i]]+1
3.只使用2張當前貨幣arr[i]情況下最少的張書,即dp[i-1][j-2*arr[i]]+2
4.只使用n張當前貨幣arr[i]情況下最少的張數,即dp[i-1][j-n*arr[i]]+n
dp[i][j]=min{dp[i-1][j-k*arr[i]]+k}(k>=0)
這裡我們把dp[i][j]提取出來
dp[i][j]=min{dp[i-1][j],dp[i-1][j-k*arr[i]]+k}(k>=1)
我們可以設k=x+1則有
dp[i][j]=min{dp[i-1][j],dp[i-1][j-arr[i]-x*arr[i]]+x+1}(x>=0)
dp[i-1][j-arr[i]-x*arr[i]]+x(x>=0)這個式子帶入第一個可以得到dp[i][j-arr[i]]
所以dp[i][j]=min{dp[i-1][j],dp[i][j-arr[i]]+1},如果j小於arr[i].則發生越界,說明arr[i]太大,一張都會超過j,所以直接dp[i][j]=dp[i-1][j],程式碼如下:
package 每日一題;
/**
* 換錢的最少次數
* 給定陣列arr,arr中所有的值都為正數且不重複。每個值代表一種面值的貨幣,每種面值的貨幣可以使用任意張,再給定一個整數aim代表要找的錢數,求組成aim的最少貨幣數。
* 舉個例子
* arr[5,2,3] ,aim=20
* 4張5元可以組成20,並且是最小的,所以返回4
* arr[5,2,3],aim=0。
* 不用任何貨幣就可以組成0元,這裡返回0.
* arr[5,2,3],ami=4
* 這裡無法組成返回-1
*
* Created by lz on 2016/5/10.
*/
public class mintimes {
/**
* 經典動態規劃的放法。如果arr的長度N,生成的行數為N,列數為aim+1的動態規劃表dp。dp[i][j]的含義是,在可以任意使用arr[0..i]貨幣的情況下,
* 組成j所需的最小張數。根據這個定義,dp[i][j]有如下計算方法
* 1.dp[0..N-1][0]的值表示找的錢數為0的時候需要的最少張數,錢數為0時,完全不需要任何貨幣所以全部設定為0.
* 2.dp[0][0..aim]表示只能使用arr[0]貨幣的情況下找某個簽署的最小張書。比如,arr[0]=2,那麼能找開的錢數為2,4,6,8,...所以令dp[0][2]=1,dp[0][4]=2,...,
* 其他位置都是找不開的,其餘一律設為32位整數最大值,我們把這個值記為max。
* 3.剩下的位置以此從左到右,再從上到下計算。
* 情況有如下:
* 1.不利用當前i的直接,即dp[i][j]=dp[i-1][j]的值
* 2.只使用1張當前貨幣arr[i]情況下最少的,即dp[i-1][j-arr[i]]+1
* 3.只使用2張當前貨幣arr[i]情況下最少的張書,即dp[i-1][j-2*arr[i]]+2
* 4.只使用n張當前貨幣arr[i]情況下最少的張數,即dp[i-1][j-n*arr[i]]+n
* dp[i][j]=min{dp[i-1][j-k*arr[i]]+k}(k>=0)
* 這裡我們把dp[i][j]提取出來
* dp[i][j]=min{dp[i-1][j],dp[i-1][j-k*arr[i]]+k}(k>=1)
* 我們可以設k=x+1則有
* dp[i][j]=min{dp[i-1][j],dp[i-1][j-arr[i]-x*arr[i]]+x+1}(x>=0)
*
* dp[i-1][j-arr[i]-x*arr[i]]+x(x>=0)這個式子帶入第一個可以得到dp[i][j-arr[i]]
* 所以dp[i][j]=min{dp[i-1][j],dp[i][j-arr[i]]+1},如果j小於arr[i].則發生越界,說明arr[i]太大,一張都會超過j,所以直接dp[i][j]=dp[i-1][j]
* @param arr
* @param aim
* @return
*/
public int minCoins(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];
//前兩種情況初始化
for (int j = 0; j <=aim; j++) {
dp[0][j]=max;
if (j-arr[0]>=0 && dp[0][j-arr[0]]!=max){
dp[0][j]=dp[0][j-arr[0]]+1;
}
}
int left = 0 ;
for (int i = 0; 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(dp[i-1][j],left);
}
}
return dp[n-1][aim] !=max ?dp[n-1][aim] : -1;
}
}