【Leetcode】322. Coin Change

题目地址:

https://leetcode.com/problems/coin-change/

给定若干硬币的面值数组 A A A和一个非负整数 x x x,问 x x x可以至少由多少个硬币拼出。如果拼不出则返回 − 1 -1 1

很经典的一道题,可以用动态规划做。设 f ( x ) f(x) f(x) x x x至少需要多少枚硬币支付,设 c ⃗ \vec{c} c 是硬币面额向量。可以这么想,如果我们能确定拼出 x x x的最优解有某个硬币 c i c_i ci,那么最优解删除掉一个硬币 c i c_i ci所得到的解,一定是拼出 x − c i x-c_i xci的最优解,否则的话我们就可以构造出一个更优解来拼出 x x x。所以我们需要做的,就是枚举最优解可能有的某个硬币。举个例子来说,假设我们有硬币 2 , 4 , 5 2,4,5 2,4,5要拼出 6 6 6,我们可以先假设最优解有一枚硬币 5 5 5,那么最优解删掉这面值为 5 5 5的硬币,一定就成为了拼出 6 − 5 = 1 6-5=1 65=1的最优解,然而拼出 1 1 1是不存在解的,所以也不存在含 5 5 5的最优解。接下来我们假设最优解有一枚硬币 4 4 4,那么最优解删掉这个 4 4 4一定得到了 6 − 4 = 2 6-4=2 64=2的最优解,而显然 f ( 2 ) = 1 f(2)=1 f(2)=1,所以我们就知道了,含硬币 4 4 4的最优解一定是一枚 4 4 4和一枚 2 2 2。然而这到底是不是全局最优解呢?不确定,因为我们还要试一下最优解含一枚 2 2 2的情况,我们发现 f ( 4 ) = 1 f(4)=1 f(4)=1,所以含 2 2 2的最优解一定是一枚 4 4 4和一枚 2 2 2,到此时,我们已经枚举完了最优解所有可能满足的情况,接着选一个全局最优的情况就行了,于是得到 f ( 6 ) = 2 f(6)=2 f(6)=2

我们可以看出来,这题本质是个搜索的问题,搜索的出口,就是当最优解已经显然可以得到的时候。接着我们发现,要求一个规模较大的最优解,我们需要频繁去求规模更小的最优解。所以为了提高效率,我们可以空间换时间,用记忆化的方式来避免重复计算。具体来说,我们可以用一个数组来记录每次得到的最优解,这样求规模更大的最优解的时候,我们就可以直接去查表,而不是再计算一遍了。

数学的递推方程角度来说,如果用 ∞ \infty 代表无法支付,那么 f f f有递推方程: f ( x ) = { 1 ,   ∃ i , x = c i 1 + min ⁡ { i : c i < x } f ( x − c i ) ,   ∃ i ,   x > c i ∞ ,   ∄ i ,   x > c i   f(x)=\begin{cases} 1,\ \exists i,x=c_i\\ 1+\min_{\{i:c_i<x\}}f(x-c_i),\ \exists i,\ x>c_i\\ \infty,\ \nexists i,\ x>c_i\ \end{cases} f(x)= 1, i,x=ci1+min{i:ci<x}f(xci), i, x>ci, i, x>ci 数学里可以用正无穷来表达无法支付,代码里却不能这么写。我们只需要用一个不可能取得到的值来标记“无法支付”就可以了。比如,我们可以用 x + 1 x+1 x+1来代表无解。

class Solution {
 public:
  int coinChange(vector<int>& coins, int amount) {
    int n = coins.size();
    int f[n + 1][amount + 1];
    fill(&f[0][0], &f[0][0] + (n + 1) * (amount + 1), amount + 1);
    f[0][0] = 0;
    for (int i = 1; i <= n; i++)
      for (int j = 0; j <= amount; j++) {
        f[i][j] = f[i - 1][j];
        if (j >= coins[i - 1])
          f[i][j] = min(f[i][j], f[i][j - coins[i - 1]] + 1);
      }

    return f[n][amount] == amount + 1 ? -1 : f[n][amount];
  }
};

时空复杂度 O ( n m ) O(nm) O(nm) n n n为硬币面值种类数, m m m为要凑出的数字。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值