題目描述:
思路:參考自《程序員代碼面試指南》--左程云著
這是一道經典的動態規劃方法,我們可以構造一個dp數組,如果arr的長度為N,則dp數組的行數為N,列數為aim+1,dp[i][j] 的含義是:在可以任意使用arr[0..i]貨幣的情況下,組成j所需要的最小張數。
明白以上定義后我們初始化第一行與第一列,第一行dp[0][0..aim]中每一個元素dp[0][j]表示用arr[0]貨幣找開面額 j所需要的最少貨幣數,此時我們只能選取arr[0]這一張貨幣,所以只有arr[0]的整數倍的面額錢才可以找開,例如當arr[0]=3,aim=10時,只能找開3,6,9的貨幣,而其他面額的則無法找開,所以將arr[0][3,6,9]初始化為1,2,3 除此之外其他值初始化為整形int的最大值INT_MAX表示無法找開。對于第一列dp[0..n][0] 中的每一個元素dp[i][0]表示用arr[i]組成面額為0的錢的最少貨幣數,完全不需要任何貨幣,直接初始化為0即可。
對于剩下的任意dp[i][j],我們依次從左到右,從上到下計算,dp[i][j]的值可能來自下面:
- 完全不使用當前貨幣arr[i]的情況下的最少張數,即dp[i-1][j]的值
- 只使用1張當前貨幣arr[i]的情況下的最少張數,即dp[i-1][j-arr[i]]+1
- 只使用2張當前貨幣arr[i]的情況下的最少張數,即dp[i-1][j-2*arr[i]]+2
- 只使用3張當前貨幣arr[i]的情況下的最少張數,即dp[i-1][j-3*arr[i]]+3
- ......
以上所有情況中,最終取張數最小的,
即dp[i][j] = min( dp[i-1][j-karr[i]]+k )( k>=0 )
=>dp[i][j] = min{ dp[i-1][j], min{ dp[i-1][j-xarr[i]]+x (1<=x) } } 接下來令x = y+1
=>dp[i][j] = min{ dp[i-1][j], min{ dp[i-1][j-arr[i]-yarr[i]+y+1 (0<=y) ] } }
又有 min{ dp[i-1][j-arr[i]-yarr[i]+y (0<=y) ] } => dp[i][ j-arr[i] ] ,(這步是這么來的:這個式子與上面的式子只相差了個1,說明這個式子少用了一個arr[i]。表達過來就是使用前i個硬幣構成 j-arr[i]這個數。)
所以,最終有:dp[i][j] = min{ dp[i-1][j], dp[i][j-arr[i]]+1 }。
如果j-arr[i] < 0,即發生了越界,說明arr[i]太大了,用一張都會超過錢數j,此時dp[i][j] = dp[i-1][j]。
代碼:
class Solution {
public int coinChange(int[] coins, int amount) {
int len = coins.length;
int[][] dp = new int[len][amount+1];
//初始化第0行,也就是只用coins[0]時
for(int i=1;i<amount+1; i++){
dp[0][i] = Integer.MAX_VALUE;
if(i>=coins[0]&&dp[0][i-coins[0]]!=Integer.MAX_VALUE){
dp[0][i] = dp[0][i-coins[0]]+1;
}
}
for(int i = 1; i< len; i++){
for(int j=1; j< amount+1; j++){
int left = Integer.MAX_VALUE;
if(j>=coins[i] && dp[i][j-coins[i]]!=Integer.MAX_VALUE){
left = dp[i][j-coins[i]]+1;
}
dp[i][j] = Math.min(dp[i-1][j],left);
}
}
return dp[len-1][amount] == Integer.MAX_VALUE?-1:dp[len-1][amount];
}
}