322.零钱兑换

💷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

提示:

  • 1 <= coins.length <= 12
  • 1 <= coins[i] <= 2^31 - 1
  • 0 <= amount <= 10^4

思路

基本思想

与518题不一样的是,本题中是从所有恰好凑成总金额的方案中选取一个硬币数最少的方案,并给出所用的硬币数,求什么就让dp数组的含义变成什么

本题中dp[j]的含义为:凑成总金额j所需要的最少硬币数

所以每次更新时都要选择最少的方案,形成的递推方程为: $$ dp[j]=min(dp[j],dp[j-coins[i]]+1) $$ dp[j-coins[i]]+1代表当前硬币加入组合中,所用的硬币数加一

执行流程

  1. 初始化dp数组,为了让dp[j]统计到最少的硬币数,也就是说一旦出现更少的硬币数就更新,所以dp数组的初始全部设为int的最大值
  2. dp[0]=0,按照示例3的要求
  3. 按照完全背包的流程进行统计
  4. 返回dp[amount]作为最终的结果,注意判断dp[amount]的大小,如果dp[amount]没有更新,代表没有组合可以组成总金额,此时返回-1

代码

根据以上分析,得出以下代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution
{
public:
    int coinChange(vector<int> &coins, int amount)
    {
        vector<int> dp(amount + 1, INT_MAX);
        dp[0] = 0;
        for (int i = 0; i < coins.size(); ++i)
        {
            for (int j = coins[i]; j <= amount; ++j)
            {
                // 如果dp[j - coins[i]]是初始值则跳过
                //这一步不可以省列,否则INT_MAX+1超过int的范围了
                if (dp[j - coins[i]] != INT_MAX)
                {
                    dp[j] = min(dp[j - coins[i]] + 1, dp[j]);
                }
            }
        }
        //当无法组成总金额时返回-1
        return dp[amount] != INT_MAX ? dp[amount] : -1;
    }
};

总结

主要是要学会改变惯性思维,求什么就让dp的含义指代什么,从而推导出递推方程

dp不是永远都代表背包所能装下的最大价值

并且要判断当前的数字相加之后会不会超过int的范围,防止出现思路对但是代码错