问题描述
问题描述
- 经典的凑硬币问题:给定不同面值的硬币,问至少需要多少枚硬币,可以凑成指定的金额。如果无法凑成,返回 -1,注意,每一硬币可以无限次使用。
- 因为每一硬币可以无限次使用,所以这是一个完全背包问题,同时,这也类似于组合问题,和 LeetCode 39. Combination Sum 类似,只不过39需要返回所有的具体路径,要用 dfs + 回溯。而该题是一个最优问题(最优子结构),用动态规划做。
dp[i][j]
:表示用[0 ~ i]中的数字加出j用到的最少硬币的数目,如果dp[i][j] = Integer.MAX_VALUE
,表示凑不出硬币。
- 既然是完全背包 + 最优子结构,那么它的状态转移条件是:
dp[i][j] = Math.min (dp[i - 1][j], dp[i][j - coins[i]]), i - 1 >= 0, j - coins[j] >= 0
,在用不用coins[i]
时取最小的一种情况,因为是完全背包,所以coins[i]
可以多次使用, 所以是dp[i][j - coins[i]]
,如果只能使用一次,那么便是普通的背包问题,那么便是dp[i - 1][j - coins[i]]
- 也可以用另一种逻辑:
dp[i][j]
: 表示用coins中前i个数字加出j用到的最少硬币的数目
经验教训
- 完全背包问题的特点
- 最优子结构问题的特点
- 该题与组合问题的联系与区别,进而到DFS 与 动态规划的联系与区别
代码实现
public int coinChange(int[] coins, int amount) {
if (coins == null || coins.length == 0) {
return 0;
}
int[][] dp = new int[coins.length][amount + 1];
dp[0][0] = 0;
for (int j = 1; j < amount + 1; ++j) {
dp[0][j] = j % coins[0] == 0 ? j / coins[0] : Integer.MAX_VALUE;
}
for (int i = 1; i < coins.length; ++i) {
dp[i][0] = 0;
for (int j = 1; j < amount + 1; ++j) {
dp[i][j] = dp[i - 1][j];
if (j - coins[i] >= 0) {
dp[i][j] = Math.min(dp[i][j], dp[i][j - coins[i]] == Integer.MAX_VALUE ? Integer.MAX_VALUE: 1 + dp[i][j - coins[i]]);
}
}
}
return dp[coins.length - 1][amount] == Integer.MAX_VALUE ? -1 : dp[coins.length - 1][amount];
}
public int coinChange(int[] coins, int amount) {
if (coins == null || coins.length == 0) {
return 0;
}
int[] dp = new int[amount + 1];
dp[0] = 0;
for (int j = 1; j < amount + 1; ++j) {
dp[j] = j % coins[0] == 0 ? j / coins[0] : Integer.MAX_VALUE;
}
for(int i = 1; i < coins.length; ++i) {
for (int j = coins[i]; j < amount + 1 ; ++j) {
dp[j] = Math.min(dp[j], dp[j - coins[i]] == Integer.MAX_VALUE ? Integer.MAX_VALUE : 1 + dp[j - coins[i]]);
}
}
return dp[amount] == Integer.MAX_VALUE ? -1 : dp[amount];
}
public int coinChange(int[] coins, int amount) {
if (coins == null || coins.length == 0) {
return 0;
}
int max = amount + 1;
int[] dp = new int[max];
dp[0] = 0;
for (int j = 0; j < max; ++j) {
dp[j] = j % coins[0] == 0 ? j / coins[0] : max;
}
for (int i = 0; i < coins.length; ++i) {
for (int j = coins[i]; j < max; ++j) {
dp[j] = Math.min(dp[j], dp[j - coins[i]] + 1);
}
}
return dp[amount] > amount ? -1 : dp[amount];
}
public int coinChange(int[] coins, int amount) {
if (coins == null || coins.length == 0) {
return 0;
}
int[][] dp = new int[coins.length + 1][amount + 1];
dp[0][0] = 0;
for (int j = 1; j < amount + 1; ++j) {
dp[0][j] = Integer.MAX_VALUE;
}
for (int i = 1; i < coins.length + 1; ++i) {
dp[i][0] = 0;
for (int j = 1; j < amount + 1; ++j) {
dp[i][j] = dp[i - 1][j];
if (j - coins[i - 1] >= 0) {
dp[i][j] = Math.min(dp[i][j], dp[i][j - coins[i - 1]] == Integer.MAX_VALUE ? Integer.MAX_VALUE: 1 + dp[i][j - coins[i - 1]]);
}
}
}
return dp[coins.length][amount] == Integer.MAX_VALUE ? -1 : dp[coins.length][amount];
}