零钱兑换 II

零钱兑换 II

零钱兑换 II是一个经典的完全背包问题。那么什么是完全背包呢?完全背包的意思是物品是可以无限使用的,背包仍然还是只能装载这么多。背包问题和完全背包问题的区别就是物品可使用次数,背包问题是只能使用一次,完全背包是不限次数。所以它们是非常相像的问题,只是细节上有一些不同。

那么既然是一个完全背包问题,这个问题肯定就是一个动态规划问题。既然是动态规划肯定就需要涉及状态转移和选择问题。

在这之前首先我们就需要定义dp[i][j],它的意思其实就是在只能选前i个硬币的面值的情况下,背包装满 j 金额的不同装法一共有多少种。

既然定义好了dp数组,下一步就是需要明确选择问题。选择什么?选择的就是到底要不要使用第i个面值的金币。那么就出现了两种情况。
①不使用第i个面值的硬币,那么背包能装满j金额就是dp[i-1][j]种,i-1就是使用前i-1个硬币就可以了,不使用第i个
②使用第i个面值的硬币,那么背包能装满j金额的总组合数就是dp[i][j-coins[i]].
相信你们在这里都会疑惑为什么第二种需要减去一个coins[i],因为这里需要确定使用了coin[i]这种面值的硬币,这样就保证了这里一定会使用第i个硬币,而后面的拼凑第i个硬币是可以重复出现的所以dp[i][j-coins[i]]的i是不需要减1的,所以这里也是完全背包问题和背包问题不一样的地方。

更重要一点是状态转移还没有结束。因为这里只是分别计算了使用还是不使用第i个硬币的总组合数。那么dp[i][j]实际上就是等于这两种情况的和。为什么?因为你确定了其中一种情况一定有第i个硬币,另一种没有。那么其实就是两大类情况了,所以这两个情况加起来才是总的组合数。

这里可能你还想问dp[i-2][j]呢?这种情况是不是也需要加进来呢?答案是不需要的,原因很简单,因为dp[i-1][j]已经包含了这种情况。翻译一下意思,然后自己计算一下就知道了。最后需要处理一下细节的问题,比如基本事件dp[?][0]也就是背包不能装东西的时候,只需要不装硬币就好了,也就是1种情况。第二种就是dp[0][?],你没有硬币了怎么凑?所以是0种。还有一个地方就是第i个硬币的面值比背包大的情况,这种情况只能使用前i-1个硬币的最佳组合数

代码(二维数组):

 public static int change(int amount,int[] coins){
        int n=coins.length;
//        +1是为了所有的基本事件能覆盖到
        int[][] dp=new int[n+1][amount+1];

//        初始化所有的值basecase
        for(int i=0;i<=n;i++){
            dp[i][0]=1;
        }

//        动态规划
        for(int i=1;i<=n;i++){
            for(int j=1;j<=amount;j++){
                if(j-coins[i-1]>=0){
                    dp[i][j]=dp[i-1][j]+dp[i][j-coins[i-1]];
                }else{
                    dp[i][j]=dp[i-1][j];
                }
            }
        }
        return dp[n][amount];
    }

最后这一道题还能再优化,我们发现每个状态都是与上一列的状态和当前行 前面的状态有关(二维数组)。所以我们可以把它变成一个一维数组。其实思路一模一样,因为dp[j]=dp[j]+dp[j-coins[i]]里面的dp[j]还是上一行的那个,所以这个状态转移是可行的。而dp[j-coins[i]]是当前行前面已经计算过的,所以也是可行的。

代码(一维数组)

class Solution {
    public int change(int amount, int[] coins) {
         int n=coins.length;
         int[] dp=new int[amount+1];
         dp[0]=1;
         for(int i=0;i<n;i++){
             for(int j=1;j<=amount;j++){
                 if(j-coins[i]>=0){
                   dp[j]=dp[j]+dp[j-coins[i]];
                 }
                
             }
         }
         return dp[amount];
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值