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

示例 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];
    }
}

笔记:

  1. 注意:因为要比较的是最小值,这个不可能的值就得赋值成为一个最大值,即 Arrays.fill(dp, amount + 1),新状态的值要参考的值以前计算出来的「有效」状态值。
    因此,不妨先假设凑不出来,因为求的是小,所以设置一个不可能的数。另外,注意边界条件。
  2. 模板一般是外层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;
    }
}

思路:

  1. 创建一个visited数组记录哪些节点已经被访问过了(节点是指不同数值的amount),访问过尝试失败的不需要再次访问了。因为一开始就要访问amount,所以amount置true。用一个队列记录访问过的节点。将所有的元素从大到小排序,这样得到的第一个step就是最短的step。
  2. 求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);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

codrab

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值