LeetCode/零钱兑换

1. 最少的硬币个数

给你一个整数数组 coins,表示不同面额的硬币;以及一个整数amount,表示总金额。
计算并返回可以凑成总金额所需的最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回-1 。
你可以认为每种硬币的数量是无限的。

跟完全平方数解法完全一致,不过完全平方转移状态用的的是满足条件的所有平方数
而零钱兑换转移状态的是能使用的硬币面值
dp[i]为整数总面额为i时的最少硬币个数
状态转移方程dp[i]=min(dp[i-coin])+1
边界条件dp[0]=0

class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        vector<int> dp(amount+1,INT_MAX/2);
        dp[0] = 0;//初始边界条件
        for(int i=1;i<=amount;i++)//遍历所有金额
            for(auto&coin:coins)//遍历所有硬币
                if(i-coin>=0) dp[i]=min(dp[i],dp[i-coin]+1);
        return dp[amount]==INT_MAX/2?-1:dp[amount];
    }
};

2. 硬币组合数

给你一个整数数组coins表示不同面额的硬币,另给一个整数 amount表示总金额。
请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0
假设每一种面额的硬币有无限个

该问无法像问题一一样设dp[i]表示总金额为i的硬币组合数
无法通过dp[i]=sum(dp[i-coin])的方式计算,这种方式其实质上得到的是排列数
这是因为dp[i-coin]已经是由多种硬币组合来的总额值,简单对诸如dp[i-coin1]、dp[i-coin2]求和会产生方案重复
我们可以先算coin1,再算考虑只加入coin2的方案更新,然后再考虑其他硬币面额,这样能使得选择硬币的时机被固定下来
形成相对位置固定的方案、从而得到真正的组合数

class Solution {
public:
    int change(int amount, vector<int>& coins) {
        int dp[amount+1];
        memset(dp, 0, sizeof(dp)); //初始化数组为0
        dp[0] = 1;
        for (int coin : coins){ //枚举硬币
            for (int j = 1; j <= amount; j++){ //枚举金额
                if (j < coin) continue; // coin不能大于amount
                dp[j] += dp[j-coin];
            }
        }
        return dp[amount];
    }
};

3. 组合数动态规划

问题二中的求解方法,我们通过固定了硬币的使用顺序,从而通过多次递推得出对应金额的组合数
那是否能使用动态规划的方法求解呢?很显然一维的描述无法通过方程反映状态的变化
可以通过固定指定金额时的硬币面值范围,来进行状态的转移,实际上也是固定硬币使用顺序
dp[k][i]表示前k种面值硬币凑成金额i的方法数
状态转移方程为 dp[k][i] = dp[k][i-coins[k-1]] + dp[k-1][i]

即用前k的硬币凑齐金额i要分为两种情况,一种是只用k-1 种硬币就凑齐了
一种是前面已经用k种硬币凑到了i-coins[k-1],还差一枚k面值硬币

边界条件dp[k][0]=1,dp[0][i]=0

二维动态规划
class Solution {
public:
    int change(int amount, vector<int>& coins) {
        int K = coins.size() + 1;
        int I = amount + 1;
        int DP[K][I];
        //初始化数组
        for (int k = 0; k < K; k++){
            for (int i = 0; i < I; i++){
                DP[k][i] = 0;
            }
        }
        //初始化基本状态
        for (int k = 0; k < coins.size() + 1; k++){
            DP[k][0] = 1;
        }
        for (int k = 1; k <= coins.size() ; k++){
            for (int i = 1; i <= amount; i++){  
                if ( i >= coins[k-1]) {
                    DP[k][i] = DP[k][i-coins[k-1]] + DP[k-1][i]; 
                } else{
                    DP[k][i] = DP[k-1][i];
                }
            }
        }
        return DP[coins.size()][amount];
    }
};

实际上进行一维优化后(即保留一行,因为对于k行更新,每次更新只跟同一列前一位置有关)
得到的代码与方法二中一模一样

posted @ 2022-05-19 14:54  失控D大白兔  阅读(180)  评论(0编辑  收藏  举报