题目描述
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
示例 1:
输入: coins = [1, 2, 5], amount = 11
输出: 3
解释: 11 = 5 + 5 + 1
示例 2:
输入: coins = [2], amount = 3
输出: -1
说明:
你可以认为每种硬币的数量是无限的。
解法一:动态规划
- 符合[最优子结构] 求amount=11 凑出amount=10的最少硬币数再选面值1的硬币就是原问题答案,硬币数没限制,所以子问题相互独立
如何列出状态转移方程
- 先确定[状态] ,就是原问题和子问题中变化的变量,由于硬币数量无限,唯一状态是目标金额amount
- 然后确定dp函数的定义: 当前目标金额是n 至少需要dp(n)个硬币凑出该金额
- 然后确定[选择]并择优: 做出什么选择会改变当前状态,在这个问题就是选择一枚硬币,目标金额减少
- 最后明确 base case: 目标金额为0,所需硬币数量为0;目标金额小于0 无解返回-1
int count=0;
public int coinChange(int[] coins,int amount) {
//base case
if(amount==0){
return 0;
}
if(amount<0){
return -1;
}
int res=Integer.MAX_VALUE;
//暴力递归
for(int coin:coins){
count=coinChange(coins,amount-coin);
if(count==-1){
continue;
}
res=Math.min(res,1+count);
}
return res!=Integer.MAX_VALUE?res:-1;
}
状态方程
递归树
时间复杂度
O(n^k)
带备忘录的递归
- 备忘录记录当前金额数记录过的计算金额数最少次数
class Solution {
int count=0;
public int coinChange(int[] coins,int amount) {
int[] memo =new int[amount+1];
return helper(coins,amount,memo);
}
public int helper(int[] coins,int amount,int[] memo){
if(amount==0){
return 0;
}
if(amount<0){
return -1;
}
if(memo[amount]!=0){
return memo[amount];
}
int res=Integer.MAX_VALUE;
for(int coin:coins){
count=helper(coins,amount-coin,memo);
if(count==-1){
continue;
}
res=Math.min(res,1+count);
}
memo[amount]=res!=Integer.MAX_VALUE?res:-1;
return memo[amount];
}
}
dp数组的迭代解法
class Solution {
public int coinChange(int[] coins, int amount) {
int[] memo = new int[amount+1];
Arrays.fill(memo,amount+1);
//别忘了初始化
memo[0]=0;
for(int i=1;i<=amount;i++){
for(int j=0;j<coins.length;j++){
if(i>=coins[j]){
memo[i]=Math.min(memo[i],memo[i-coins[j]]+1);
}
}
}
return memo[amount]>amount?-1:memo[amount];
}
}
方法二: 贪心+回溯
class Solution {
//ans答案用全局方便点,不用考虑该变量一直变换回不来的问题
int ans=Integer.MAX_VALUE;
public int coinChange(int[] coins, int amount) {
if(amount==0){
return 0;
}
Arrays.sort(coins);
dfs(coins,amount,coins.length-1,0);
return ans==Integer.MAX_VALUE?-1:ans;
}
public void dfs(int[] coins,int amount,int index,int count){
//找到后会继续搜索,看有没有其他的更小的count
if(amount==0){
ans=Math.min(ans,count);
return;
}
if(index<0){
return;
}
//有k+count<ans,保证超过ans的不继续计算,实现剪枝
for(int k=amount/coins[index];k>=0&&k+count<ans;k--){
//深入找,找到了返回,再尝试其他值
dfs(coins,amount-k*coins[index],index-1,count+k);
}
}
}
扩展
- 写了一个数组最少金额的组合数的代码
package findMoney;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
class Solution {
List<Integer> list;
List<Integer> temp;
int ans=Integer.MAX_VALUE;
boolean flag;
public int coinChange(int[] coins, int amount) {
list=new ArrayList<>();
temp=new ArrayList<>();
if(amount==0){
return 0;
}
dfs(coins,amount,coins.length-1,0);
return ans==Integer.MAX_VALUE?-1:ans;
}
public void dfs(int[] coins,int amount,int index,int count){
if(amount==0){
if (count<ans){
ans=count;
list=new ArrayList<>(temp);
}
return;
}
if(index<0){
return;
}
for(int k=amount/coins[index];k>=0&&k+count<ans;k--){
flag=false;
if(flag){
temp.add(coins[index]);
}
else{
for(int i=0;i<k;i++) {
temp.add(coins[index]);
flag = true;
}
}
dfs(coins,amount-k*coins[index],index-1,count+k);
}
}
public static void main(String[] args) {
int[] nums = {1,2,5,10,50};
Random random=new Random();
Solution solu = new Solution();
int sumAmount=random.nextInt(100);
int paid=random.nextInt(100)%(100-sumAmount+1)+sumAmount;
int back=paid-sumAmount;
System.out.println(sumAmount);
System.out.println(paid);
System.out.println(back);
System.out.println(solu.coinChange(nums,back));
System.out.println(solu.list);
}
}