322.给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。
你可以认为每种硬币的数量是无限的。
示例 1:
输入:coins = [1, 2, 5], amount = 11
输出:3
解释:11 = 5 + 5 + 1
示例 2:
输入:coins = [2], amount = 3
输出:-1
示例 3:
输入:coins = [1], amount = 0
输出:0
- 动态规划:假设 dp[i] 为 amount 为 i 时的最少硬币数。那么我们首先可以知道的是 dp[0] = 0,并且 dp[coin] = 1,比如硬币面额 coin 为 2,那么 dp[2] 一定为 1,一个 2 元硬币就能使得总和为 2。那么 dp[i] 呢,最好的结果肯定是 dp[i-coin] + 1,比如你有 [1,2,5] 三种硬币, 当你要凑到 7 元时你会想,如果我们得到了凑成 6 元的最小硬币数,再加一个 1 元硬币就得到了凑成 7 元的一种可能,即 dp[7] = dp[7-1] + 1,或者我们之前已经得到了凑成 5 元的最小硬币数,再加一个 2 元硬币也能凑成 7 元,即 dp[7] = dp[7-2] + 1,我们取 min 即可
-
public int coinChange(int[] coins, int amount) { 服务器托管网 int[] dp = new int[amount+1]; Arrays.fill(dp, -1); dp[0] = 0; for(int c:coins){ // 硬币面值首先肯定得小于等于我所需的总数 if(camount)dp[c]=1; } for(int i=1;iamount;i++){ // 得到过了就跳过 if(dp[i]服务器托管网!=-1)continue; int min = Integer.MAX_VALUE; for(int c:coins){ // 我们需要得到总共 i 元,硬币面额肯定首先得小于 i,等于的我们已经初始化过了 // 并且我们在计算比如 dp[7] = dp[7-2] + 1 时,我们肯定首先得保证 dp[7-2] 存在 // 不然怎么往后推 if(c i && dp[i-c]!=-1)min=Math.min(min,dp[i-c]); } if(min != Integer.MAX_VALUE)dp[i]=min+1; } return dp[amount]; }
- 这里附上别人更简洁的写法,无法凑成的情况就用 int 最大值表示
-
public int coinChange(int[] coins, int amount) { if(coins.length == 0){ return -1; } //dp[i]表示金额i最少需要的硬币数 int[] dp = new int[amount+1]; dp[0] = 0; for(int i=1;iamount;i++){ int min = Integer.MAX_VALUE; for (final int coin : coins) { // 这里 dp[i - coin] // 而且因为无法凑成的 dp[x] 为 int 最大值,所以 dp[x] 如果凑不成就不可能 // 所以它同时也排除了无法凑成的情况 // 当然你也可以改写成 // if (i - coin >= 0) min = Math.min(min,dp[i - coin]); // 然后 min 不为 int 最大值才 + 1,否则上面就加 1 就超出 int 最大值了 // dp[i] = min!=Integer.MAX_VALUE?min+1:Integer.MAX_VALUE; if (i - coin >= 0 && dp[i - coin] min) { min = dp[i - coin] + 1; } } dp[i] = min; } return dp[amount]== Integer.MAX_VALUE ? -1 : dp[amount]; }
- dfs(超时):我们很容易想到的就是像 n 叉树一样尝试每种组合,比如 [1,2] 我们从这两个硬币开始分叉
(1|2) -> (11,12 | 21,22) -> (111,112 | 121,122 | 211,212 | 221,222)......
-
int MAX = Integer.MAX_VALUE; int[] coins; int ans=MAX; public int coinChange(int[] coins, int amount) { this.coins = coins; dfs(amount,0); return ans==MAX?-1:ans; } // amount:所需总数 // count:硬币数量 public void dfs(int amount,int count){ // 减到总数小于 0 肯定不行 if(amount 0)return; // 减到总数为 0 说明凑出一种可能了,记录下来 if(amount==0){ ans=Math.min(ans,count); } for(int coin:coins){ dfs(amount-coin, count+1); } }
- 上面代码会超时,是因为其实组成比如12 和 21 时得到的总数是相同的都是总共 3 元,所以会包含大量重复的计算,那么我们很容易想到的就是用记忆化搜索改进,其实这和 dp 的原理很像
-
int MAX = Integer.MAX_VALUE; int[] coins; int[] memo; public int coinChange(int[] coins, int amount) { memo = new int[amount+1]; this.coins = coins; return dfs(amount); } public int dfs(int amount){ if(amount == 0)return 0; if(amount 0)return -1; // 有了这个我们才减少了很多重复计算 if(memo[amount] != 0)return memo[amount]; int min = MAX; for(int coin:coins){ int res = dfs(amount-coin); // res == -1 可以理解为 dp[amount-coin] 不存在,所以你会发现记忆化搜索都能改写成 dp // 所以 dp[amount-coin] 存在才能推出在此基础上加一个 coin 凑成 amount,推出 dp[amount] if(res!=-1)min = Math.min(min,res); } // 每个 coin 对应的 dp[amount-coin] 都不存在时 min 就保持不变还是 int 最大值 // 这就表示我们无法用这些硬币得到总数 amount,所以为 -1 memo[amount]=min==MAX?-1:min+1; return memo[amount]; }
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net