【刷题1】Leetcode 322. 零钱兑换 java题解

博客围绕LeetCode上的硬币问题展开,指出N刷时将i>=coins[j]放for循环判断条件导致解答错误的原因。该问题是完全背包问题,介绍了自底向上的动态规划和递归两种解法,分析了动态规划的复杂度,递归会超时。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

N刷犯得一个错误:将i>=coins[j]放在了for循环判断条件中,导致解答错误。为什么在《完全平方数》就可以呢,因为那里的平方数是有序递增的,而这里的硬币大小不是有序的。

题目

https://leetcode-cn.com/problems/coin-change/
类似题目:279 完全平方数
在这里插入图片描述

完全背包问题

硬币数量是无限次使用的,所以是一个完全背包问题。
完全背包,需要正序遍历背包。

初始状态:总金额为0,背包容量为0时,没有硬币能凑成结果。
由于转移时比较min,所以初始化时要设置一个最大值。如果默认初始化dp[j]=0,就无法转移状态。

我们要求的是凑成amount的最少硬币数。
转移状态:dp[j]=Math.min(dp[j],dp[j-coins[i]]+1);
凑成j-coins[i]的硬币数,加上这一枚硬币i,总价值加上coins[i]的面值就是j。

求排列时,外层遍历背包,内层遍历物品(因为物品出现的不同顺序视为不同的排列)。
求组合时,外层可以遍历物品也可以遍历背包。

求种类数时,我们将dp数组的长度设为amount+1,也就是容量从0到amount。这是因为要考虑容量为0的情况,这是不会出现的情况,但是是一种初始情况,用于后续的状态转移。

class Solution {
    public int coinChange(int[] coins, int amount) {
        if(amount==0) return 0;
        //Arrays.sort(coins);
        int[] dp=new int[amount+1];
        Arrays.fill(dp,10010);
        dp[0]=0;
        for(int i=0;i<coins.length;i++){
            for(int j=coins[i];j<=amount;j++){
                dp[j]=Math.min(dp[j],dp[j-coins[i]]+1);
            }
        }
        return dp[amount]==10010?-1:dp[amount];
    }
}

方法一:自底向上的动态规划

代码

class Solution {
    /*
    自底向上的动态规划
    */
    public int coinChange(int[] coins, int amount) {
        if(coins.length==0) return -1;
        int[] memo=new int[amount+1];
        Arrays.fill(memo,amount+1);//初始化为一个不可能到达的数量amount+1。设置为Integer.MAX_VALUE会出错
        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);
                    //memo[i]的两种情况:包含coins[j],不包含conis[j]
                }
            }
        }
        if(memo[amount]==amount+1)//没结果
            return -1;
        else
            return memo[amount];
    }
}

复杂度

coins长度为m,amount为n
时间复杂度O(mn):对1…amount取值,需要枚举coins。
空间复杂度O(n):dp数组占用的空间

结果

在这里插入图片描述

没有将数组初始化为最大值

由于在更新过程中,要取最小值min,如果数组初始化默认为0,那么在比较时就都会选择0。会出现结果0的错误。
各种错误情况,进行了分别判断。

class Solution {
    public int coinChange(int[] coins, int amount) {
        if(amount==0) return 0;
        int[] dp=new int[amount+1];
        dp[0]=0;//凑0元,需要0个硬币
        for(int i=1;i<=amount;i++){
            for(int j=0;j<coins.length;j++){//遍历硬币
                if(coins[j]<=i){//硬币面额<=总金额
                    //dp[i-coins[j]]//凑成i-coins[j]所需要的硬币数
                    if(i-coins[j]!=0&&dp[i-coins[j]]==0) continue;
                    //x总数不为0,但没有一种方法能组成dp[x]=0。
                    if(dp[i]!=0)
                         //dp[i]已经被上一个小的硬币更新过,尝试用当前硬币更新
                         dp[i]=Math.min(dp[i],dp[i-coins[j]]+1);
                    else
                         dp[i]=dp[i-coins[j]]+1;                    
                }
            }
        }
        return dp[amount]==0?-1:dp[amount];
    }
}
/**
给定容量amount,求最小物品数,物品可以重复使用,所以是完全背包。
 */

方法二 递归(会超时)

class Solution {
    int res=Integer.MAX_VALUE;
    public int coinChange(int[] coins, int amount) {
        find(coins,amount,0);
        return res==Integer.MAX_VALUE?-1:res;
    }
    public void find(int[] coins,int amount,int count){
        if(amount<0){
            return;
        }
        if(amount==0){
            res=Math.min(res,count);
        }
        for(int coin:coins){
            find(coins,amount-coin,count+1);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值