题目
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
【举例】
输入: coins = [1, 2, 5], amount = 11
输出: 3
解释: 11 = 5 + 5 + 1
分析
我们仍采取上次说的回溯转动态规划,来慢慢熟悉动态规划解题思路。
回溯
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| public int minCoins(int[] coins, int amount){ if(coins == null || coins.length == 0 || amount < 1){ return -1; } return proress(coins, 0, amount); }
public int proress(int[] coins, int i, int rest) { if(i == coins.length){ return rest == 0 ? 0 : -1; } int res = -1; for(int k = 0; k * coins[i] <= rest; k++){ int next = proress(coins, i +1, rest - k* coins[i]); if(next != -1){ if(res == -1) res = next + k; else res = Math.min(res, next + k); } } return res; }
|
动态规划
- 找可变参数,如果看了回溯的代码,马上能确定分别是:硬币面值
coins[i]
、剩下的钱rest
。
构建表,二维数组的行代表硬币面值,列代表兑换面值,如下图所示。注意,为了构建base case,我们必须要创建0硬币面值,以及0兑换钱数,因为我把0面值放在了数组最后,所以我们运算方向是自下向上的
随后我们对base case进行初始赋值,当0面值时。只有当兑换钱总数为0,数量才是0,其他都是-1(不可达)
最为关键的一步,如何填写非base case,我们要梳理清楚逻辑关系,首先赋值-1,然后我们考虑此时的coin[i]
能否构成,不能构成(coin[i] > rest)我们肯定不可达,如果coins[i] < rest ,这是就要看 rest - coins[i] 是否可达,如果不可达,那rest也不可达,如果可达,我们就要和之前没有coins[i]参与的面值组成的rest比一比,谁更小。
以上这段文字需要细细品味,如果你觉得想起来有点吃力,请看下面两张图:
这两张图中,我希望你搞清楚红色数字的来由,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public int minCoins(int[] coins, int amount){ if(coins == null || coins.length == 0 || amount < 1){ return -1; } int N = coins.length; int[][] dp = new int[N + 1][amount + 1]; for(int col = 1; col <= amount; col++){ dp[N][col] = -1; } for(int i = N - 1; i >= 0; i--){ for(int rest = 0; rest <= amount; rest++){ dp[i][rest] = -1; if(dp[i + 1][rest] != -1) dp[i][rest] = dp[i + 1][rest]; if(rest - coins[i] >= 0 && dp[i][rest - coins[i]] != -1){ if(dp[i][rest] == -1) dp[i][rest] = dp[i][rest - coins[i]] + 1; else dp[i][rest] = Math.min(dp[i][rest - coins[i]] + 1, dp[i][rest]); } } } return dp[0][amount]; }
|
最后,欢迎大家来我的公众号看一看,周更一些算法总结和Java基础巩固~