题目:给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
示例 1:
输入: coins = [1, 2, 5], amount = 11
输出: 3
解释: 11 = 5 + 5 + 1
示例 2:
输入: coins = [2], amount = 3
输出: -1
说明:
你可以认为每种硬币的数量是无限的。
思路:
因为每种硬币可以使用无数次,所以这是一道完全背包问题;
和01背包问题相似,对于一种货币只要条件允许,我可以选择要或者不要,只不过在这道题中,可以要多次;
先构造dp数组:
(1)假定coins数组的长度为n,那么dp数组大小:dp[n][amount + 1];
(2)dp[i][j]的含义是:当前要找的零钱总数是j,当前可以使用的零钱种类是coins[0…i]时,最少的硬币个数(如果可以凑成的话);
(3)dp数组第一行的含义:当零钱只有coins[0]时,对应0~amount这些金额需要多少个硬币,如果无法正好凑成,我们将这个位置设置成正型的最大值,作为判断;
(4)dp数组第一列的含义:当所需金额为0时,不需要任何零钱就可以凑成,所以第一列都是0;
(5)dp[i][j]与两个位置的元素有关系:
当不想或者不能使用当前种类的硬币时:dp[i][j] = dp[i - 1][j];
当使用了当前种类的硬币时:dp[i][j] = dp[i][j - coins[i]] + 1;
这里我们还需要额外的判断,是否无论我当前如何选择,都无法正好凑成需要的钱数;
Java代码:
public static int solution(int[] arr,int aim) {
int[][] dp = new int[arr.length][aim + 1];
int max = Integer.MAX_VALUE;
for(int i = 1; i <= aim; i++) {
dp[0][i] = max;
if(i >= arr[0] && dp[0][i - arr[0]] != max) {
dp[0][i] = dp[0][i - 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] && 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[arr.length - 1][aim] == max ? -1 : dp[arr.length - 1][aim];
}