【leetcode】518. 零钱兑换 II(动态规划,dp[k,i]表示在coins[0...k-1]中选取硬币可以凑成总金额i的组合数)

目录

  • 参考
  • 题目
  • 思路:动态规划,注意是组合,不是排列
  • 降维优化

参考

leetcode优秀分析

题目

这次不求凑成总金额所需的最少硬币个数,而是求组合总数。
给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。

示例 1:
输入: amount = 5, coins = [1, 2, 5]
输出: 4
解释: 有四种方式可以凑成总金额:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1

示例 2:
输入: amount = 3, coins = [2]
输出: 0
解释: 只用面额2的硬币不能凑成总金额3。

示例 3:
输入: amount = 10, coins = [10] 
输出: 1
 
注意:
你可以假设:
0 <= amount (总金额) <= 5000
1 <= coin (硬币面额) <= 5000
硬币种类不超过 500 种
结果符合 32 位符号整数

思路:动态规划,注意是组合,不是排列

class Solution {
public:
    /*
    动态规划。设状态方程dp[i]表示凑成总金额i的组合数,我们要求的是dp[amount]。初始状态dp[0]=1,只有有人到了这步说明刚好满足所以+1
    状态转移方程:对于每一种硬币coins[j],如果不放那组合数没变化;如果放了,那么组合数有dp[i-coins[j]]。因此dp[i] = dp[i] + dp[i-coins[j]]
    但是这个有问题!我们会把1,2和2,1当成两成情况处理,计算出来的数是排列数而不是组合数!
    int change(int amount, vector& coins) {
        if( amount<=0 || coins.size()==0 ){
            return 0;
        }
        vector dp(amount+1, 0); //dp初始化为0
        // for(int i=0; i<=amount; i++)    dp[i] = 0;
        dp[0] = 1;
        for(int i=1; i<=amount; i++){
            for(int j=0; j= coins[j] ){
                    dp[i] = dp[i] + dp[i-coins[j]];
                }
            }
        }
        return dp[amount];
    }
    */
    /*
    我们的是求组合数,而不是排列数,所以无关顺序。思路可以理解成哪几个空用来放硬币a,哪几个空用来放硬币b。也就是对每一种硬币coins[k],我们都遍历一遍dp[k,0...amount],dp[k,i]表示第0..k种硬币能凑i的组合数,即前k+1种硬币能凑i的组合数。
    动态规划:dp[k,i]表示在coins[0...k-1]中选取某类硬币可以凑成总金额i的组合数,我们最终要求的是dp[coins.size(), amount]。注意dp都扩容了1,这是为了初始化以及最终求解。
    状态初始化dp[..][..]=0,但是dp[..,0]=1。
    状态转移方程:当前考虑硬币为coins[k],要么放,要么不放。如果不放,那就是上一次已经凑足了,即dp[k-1,i]。如果放的话,那么就是之前已经凑足i-coins[k]的钱,只差现在这一个了,即dp[k, i-coins[k]],注意不是dp[k-1, i-coins[k]]!!两种情况加起来,dp[k,i] = dp[k-1,i] + dp[k, i-coins[k]]
    然后由于每一次都只跟上一次状态相关,所以我们可以考虑降维优化.
    */
    int change(int amount, vector<int>& coins) {
        if( amount==0 ){ //默认金额就是0,所以存在一种组合数,那就是谁都不放
            return 1;
        }
        if( coins.size()==0 ){ //没有硬币
            return 0;
        }
        int dp[coins.size()+1][amount+1];
        //数组初始化
        for(int k=0; k<=coins.size(); k++){
            for(int i=0; i<=amount; i++){
                dp[k][i] = 0;
            }
        }
        //状态初始化:到达金额0的都是符合要求
        for(int k=0; k<=coins.size(); k++){
            dp[k][0] = 1; 
        }
        //从coins[0...k-1]中自由选取硬币,凑成总金额i。所以两层循环
        for(int k=1; k<=coins.size(); k++){
            //当前硬币 coins[k-1]
            for(int i=1; i<=amount; i++){
                if( i >= coins[k-1] ){
                    //当前硬币选择:不放 or 放
                    dp[k][i] = dp[k-1][i] + dp[k][i-coins[k-1]]; //注意判断i-coins[k]
                }else{
                    dp[k][i] = dp[k-1][i];
                }
            }
        }
        return dp[coins.size()][amount];
    }
};

降维优化

class Solution {
public:
    /*
    我们的是求组合数,而不是排列数,所以无关顺序。思路可以理解成哪几个空用来放硬币a,哪几个空用来放硬币b。也就是对每一种硬币coins[k],我们都遍历一遍dp[k,0...amount],dp[k,i]表示第0..k种硬币能凑i的组合数,即前k+1种硬币能凑i的组合数。
    动态规划:dp[k,i]表示在coins[0...k-1]中选取硬币可以凑成总金额i的组合数,我们最终要求的是dp[coins.size(), amount]。注意dp都扩容了1,这是为了初始化以及最终求解。
    状态初始化dp[..][..]=0,但是dp[...,0]=1。
    状态转移方程:当前考虑硬币为coins[k],要么放,要么不放。如果不放,那就是上一次已经凑足了,即dp[k-1,i]。如果放的话,那么就是之前已经凑足i-coins[k]的钱,只差现在这一个了,即dp[k, i-coins[k]],注意不是dp[k-1, i-coins[k]]!!两种情况加起来,dp[k,i] = dp[k-1,i] + dp[k, i-coins[k]]
    然后由于每一次都只跟上一次状态相关,所以我们可以降维优化
    */
    int change(int amount, vector<int>& coins) {
        if( amount==0 ){ //默认金额就是0,所以存在一种组合数,那就是谁都不放
            return 1;
        }
        if( coins.size()==0 ){ //没有硬币
            return 0;
        }
        //dp[i]表示在coins[0...k-1]中选取硬币可以凑成总金额i的组合数
        int dp[amount+1];
        //数组初始化
        for(int i=0; i<=amount; i++){
            dp[i] = 0;
        }
        //状态初始化:到达金额0的都是符合要求、
        dp[0] = 1; 
        //从coins[0...k-1]中自由选取硬币,凑成总金额i。所以两层循环
        for(int k=1; k<=coins.size(); k++){
            //当前硬币 coins[k-1]
            for(int i=1; i<=amount; i++){
                if( i >= coins[k-1] ){
                    //当前硬币选择:不放 or 放
                    dp[i] = dp[i] + dp[i-coins[k-1]]; //注意判断i-coins[k]
                }else{
                    dp[i] = dp[i];
                }
            }
        }
        return dp[amount];
    }

};

你可能感兴趣的:(leetcode)