题目
给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。
你可以认为每种硬币的数量是无限的。
示例:
输入: coins = [1, 2, 5], amount = 11
输出: 3
解释: 11 = 5 + 5 + 1
暴力递归
class Solution {
public int coinChange(int[] coins, int amount) {
return dp(coins, amount);
}
int dp(int[] coins, int amount){
if(amount == 0) return 0;
if(amount < 0) return -1;
int res = Integer.MAX_VALUE;
for(int coin : coins){
int subProblem = dp(coins, amount - coin);
if(subProblem == -1) continue;
res = Math.min(res, subProblem + 1);
}
return res == Integer.MAX_VALUE ? -1 : res;
}
}
首先这个问题属于动态规划问题,因为它具有最优子结构。要符合最优子结构,子问题之间必须相互独立。
假设面值为1,2,5想求amount = 11的最少硬币书,只需要知道amount = 10 9 6的最少硬币数(子问题),然后把子问题加一即可。
同斐波那契数列,暴力破解后仍有大部分计算重复计算需要减少计算量,假设目标金额为K,给定硬币数为K,那么最坏的情况为全是一元硬币,树的高度为N,假设为满K叉树,则节点总数为O(),由于每次递归包含一个for循环,则时间复杂度为O()。
带"备忘录"的递归
类似之前斐波那契数列。
class Solution {
int[] memo;
public int coinChange(int[] coins, int amount) {
memo = new int[amount + 1];
Arrays.fill(memo, -2);
return dp(coins, amount);
}
int dp(int[] coins, int amount){
if(amount == 0) return 0;
if(amount < 0) return -1;
if(memo[amount] != -2) return memo[amount];
int res = Integer.MAX_VALUE;
for(int coin : coins){
int subProblem = dp(coins, amount - coin);
if(subProblem == -1) continue;
res = Math.min(res, subProblem + 1);
}
memo[amount] = (res == Integer.MAX_VALUE) ? -1 : res;
return memo[amount];
}
}
dp数组的迭代解法
也可以使用自底向上的DP数组来消除重叠子问题。DP数组的定义,当目标为i时,至少需要dp[i]个硬币凑出目标金额。
class Solution {
public int coinChange(int[] coins, int amount) {
int[] dp = new int[amount+1];
Arrays.fill(dp, amount+1);
dp[0] = 0;
for(int i = 0; i < dp.length; i++){
for(int coin : coins){
if(i - coin < 0){
continue;
}
dp[i] = Math.min(dp[i], 1 + dp[i - coin]);
}
}
return (dp[amount] == amount + 1) ? -1 : dp[amount];
}
}
ps:因为凑成amount金额的硬币数最多为amount,所以数组的初始值为amount+1,为什么不直接初始化为Integer.MAX_VALUE,因为后面又dp[i - coin] + 1,导致整形溢出。
解题思路参考labuladong的算法笔记1.3.2章。