1. 程式人生 > >每日一題之動歸-換錢的最少次數(一)

每日一題之動歸-換錢的最少次數(一)

題目:
給定陣列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; } }