分析
如果arr的长度为N, 则生成一个行数为N, 列数为aim+1的动态规划表dp[N][aim+1], dp[i][j]的含义为:在可以任意使用arr[0…i]货币的情况下,组成j所需的最小张数。
设: arr=[5,2,3,1] aim = 5
1、dp[0…N-1][0]的值表示找钱数为0时需要的最少张数,所以全设为0。(矩阵的第一列)
2、dp[0][0…aim]的值表示只能使用arr[0]货币也就是5的情况下,找0 ,1,2,3,4,5的钱的情况下。其中无法找开的一律设为32位的最大值,记为max.
3、剩下的位置依次从左到右,再从上到下计算。假设计算到(i,j)位置,dp[i][j]的值可能来自下面的情况:
完全不使用当前货币arr[i]情况系的最少张数,即dp[i-1][j]的值
只使用一张当前货币arr[i]的情况下的最少张数,即dp[i-1][j-arr[i]]+1 其中 j-arr[i]的值为使用了一张arr[i]后,还需要找多少钱。 i-1是指使用arr[i]之前的钱来兑换
只使用两张当前货币arr[i]的情况下的最少张数,即dp[i-1][j-2arr[i]]+2
只使用三张当前货币arr[i]的情况下的最少张数,即dp[i-1][j-3arr[i]]+3
所有情况中,取最小的纸张数。所以:
dp[i][j] = min{dp[i-1][j], min{dp[i-1][j-x*arr[i]]+x (x >= 1)}} ==>
设x-1 = y >= 0 ==> x = y +1代入得
dp[i][j] = min{dp[i-1][j], min{dp[i-1][j-arr[i]-y*arr[i]]+y+1 (y>=0)}}
又因为min{dp[i-1][j-arr[i]-yarr[i]+ y] = dp[i][j-arr[i]] ,因为其中 dp[i-1][j-yarr[i]+y] = dp[i][j]
最终有: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]即可。
代码:
import java.util.*;
public class Solution {
/**
* 最少货币数
* @param arr int整型一维数组 the array
* @param aim int整型 the target
* @return int整型
*/
public int minMoney (int[] arr, int aim) {
if(arr == null || arr.length == 0 || aim < 0){
return -1;
}
int[][] dp = new int[arr.length][aim+1];
int max = Integer.MAX_VALUE;
//设置第一行
for(int j=1; 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=1; i < arr.length; 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]);
}
}
return dp[arr.length-1][aim] != max ? dp[arr.length-1][aim] : -1;
}
}