- 零钱兑换
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
示例 1:
输入: coins = [1, 2, 5], amount = 11
输出: 3
解释: 11 = 5 + 5 + 1
示例 2:
输入: coins = [2], amount = 3
输出: -1
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/coin-change
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路一:暴力递归
假设coins数组中包含1 5 20 25四种硬币,则amout个硬币所需的最小硬币个数为 amount-1, amount - 5, amout - 20, amount - 25中最小硬币个数再 +1。 自顶向下调用,出现了重叠子问题。 代码如下:
1
2
3
4
5
6
7
|
static int coinChange(int n) {
if (n < 1) return Integer.MAX_VALUE;
if (n == 25 || n == 20 || n == 5 || n == 1) return 1;
int min1 = Math.min(coins1(n - 25), coins1(n - 20));
int min2 = Math.min(coins1(n - 5), coins1(n - 1));
return Math.min(min1, min2) + 1;
}
|
思路二:记忆化搜索
跟上述代码思想类似,自顶向下调用,用dp数组记录计算过的值。 代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
/**
* 记忆化搜索(自顶向下的调用)
*/
static int coins2(int n) {
if (n < 1) return -1;
int[] dp = new int[n + 1];
int[] faces = {1, 5, 20, 25};
for (int face : faces) {
if (n < face) break;
dp[face] = 1;
}
return coins2(n, dp);
}
static int coins2(int n, int[] dp) {
if (n < 1) return Integer.MAX_VALUE;
if (dp[n] == 0) {
int min1 = Math.min(coins2(n - 25, dp), coins2(n - 20, dp));
int min2 = Math.min(coins2(n - 5, dp), coins2(n - 1, dp));
dp[n] = Math.min(min1, min2) + 1;
}
return dp[n];
}
|
思路三:动态规划
采用动态规划,自下向上递推出最终结果。
定义状态:dp数组中存放,当获取i个数量的零钱时,所需最小的硬币个数。
定义初始值:最初数组中存放比amout还大的数,用于最终判断如果>amount,说明凑不到零钱,则返回-1。 赋值dp[0] = 0;
定义状态转移方程:把coins数组遍历,dp[i] 为 dp[i - coints[0]],dp[i - coints[1]],dp[i - coints[2]]…中的最小值 + 1;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
public static int coinChange(int[] coins, int amount) {
if (amount < 1) return 0;
// 定义状态 凑到 amount 硬币需要的最少硬币个数
int[] dp = new int[amount + 1];
// 定义初始值
Arrays.fill(dp,amount + 1);
dp[0] = 0;
// 定义状态转移方程
for (int i = 1; i <= amount; i++) {
for (int j = 0; j < coins.length; j++) {
if (i >= coins[j]) dp[i] = Math.min(dp[i - coins[j]] + 1,dp[i]);
}
}
return dp[amount] > amount ? -1 : dp[amount];
}
|
时间复杂度: O(N)
空间复杂度: O(N)