题目
给定不同面额的硬币 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
示例 4:
输入:coins = [1], amount = 1
输出:1
示例 5:
输入:coins = [1], amount = 2
输出:2
题解一(dp)
class Solution {
public int coinChange(int[] coins, int amount) {
int[] dp=new int[amount+1];//dp[i]表示总量为i时的最少硬币个数
//状态转移方程:dp[j]=min(dp[j],dp[j-coins[i]]+1)这个是完全背包问题,硬币无限量
Arrays.fill(dp,amount+1);
dp[0]=0;//注意边界条件
for(int coin:coins){
for(int i=coin;i<=amount;i++){
if(i>=coin){//余额等于硬币值时,该硬币也可取
dp[i]=Math.min(dp[i],dp[i-coin]+1);
}
}
}
return dp[amount]==amount+1?-1:dp[amount];
}
}
笔记:
- 注意:因为要比较的是最小值,这个不可能的值就得赋值成为一个最大值,即 Arrays.fill(dp, amount + 1),新状态的值要参考的值以前计算出来的「有效」状态值。
因此,不妨先假设凑不出来,因为求的是小,所以设置一个不可能的数。另外,注意边界条件。 - 模板一般是外层coin循环,内层j循环到amount,完全背包是正序,01是倒序。
题解二(BFS)
class Solution {
public int coinChange(int[] coins, int amount) {
if(amount==0){
return 0;
}
Arrays.sort(coins);
Queue<Integer> queue=new LinkedList<>();
boolean[] visited=new boolean[amount+1];
visited[amount]=true;
queue.offer(amount);
int count=1;
while(!queue.isEmpty()){
int size=queue.size();
for(int i=0;i<size;i++){//在一开始就把size固定下来!因为后面循环的时候队列里还会添加元素!size会变化!这个for循环相当于处理了一行的元素!
int front=queue.poll();
for(int coin:coins){//循环三次就跳出来了
int next=front-coin;
if(next==0){
return count;//因为coins排过序了,所以得到的必定是最小的count
}
if(next<0){
break;
}
if(!visited[next]){
visited[next]=true;//避免节点的重复访问
queue.offer(next);
}
}
}
count++;//走完一层之后,深度加一
}
return -1;
}
}
思路:
- 创建一个visited数组记录哪些节点已经被访问过了(节点是指不同数值的amount),访问过尝试失败的不需要再次访问了。因为一开始就要访问amount,所以amount置true。用一个队列记录访问过的节点。将所有的元素从大到小排序,这样得到的第一个step就是最短的step。
- 求bfs树的时候每个节点间的差值为coin相关,count代表了树的层数,也就是0到amount的最小步数。
题解三(DFS深度优先+剪枝)
class Solution {
int ans=Integer.MAX_VALUE;
public int coinChange(int[] coins, int amount) {
Arrays.sort(coins);
dfs(coins,coins.length-1,amount,0);
return ans==Integer.MAX_VALUE?-1:ans;
}
public void dfs(int[] coins,int index,int amount,int cnt){
if(index<0){
return;
}
for(int c=amount/coins[index];c>=0;c--){
int na=amount-c*coins[index];
int ncnt=cnt+c;
if(na==0){
ans=Math.min(ans,ncnt);
break;//剪枝1
}
if(ncnt+1>=ans){
break; //剪枝2
}
dfs(coins,index-1,na,ncnt);
}
}
}//超时了
class Solution {
int ans=Integer.MAX_VALUE;
public int coinChange(int[] coins, int amount) {
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){
if(index<0){//考虑边界条件
return;
}
for(int i=amount/coins[index];i>=0;i--){
int newAmount=amount-i*coins[index];
int newCount=count+i;
if(newAmount==0){//如果amount用完了,看看是否要更新ans
ans=Math.min(ans,newCount);
break;
}
if(newAmount+1>=ans){
break;
}
dfs(coins,newAmount,index-1,newCount);
}
}
}