518.coin-change-2.md 4.7 KB
Newer Older
L
luzhipeng 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220

## 题目地址
https://leetcode.com/problems/coin-change-2/description/

## 题目描述
```
You are given coins of different denominations and a total amount of money. Write a function to compute the number of combinations that make up that amount. You may assume that you have infinite number of each kind of coin.

 

Example 1:

Input: amount = 5, coins = [1, 2, 5]
Output: 4
Explanation: there are four ways to make up the amount:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1
Example 2:

Input: amount = 3, coins = [2]
Output: 0
Explanation: the amount of 3 cannot be made up just with coins of 2.
Example 3:

Input: amount = 10, coins = [10] 
Output: 1
 

Note:

You can assume that

0 <= amount <= 5000
1 <= coin <= 5000
the number of coins is less than 500
the answer is guaranteed to fit into signed 32-bit integer

```
## 思路
这个题目和coin-change的思路比较类似。

我们还是按照coin-change的思路来, 如果将问题画出来大概是这样:

![coin-change-2](../assets/problems/coin-change-2.png)


进一步我们可以对问题进行空间复杂度上的优化(这种写法比较难以理解,但是相对更省空间)

![coin-change-2-opt](../assets/problems/coin-change-2-opt.png)

> 这里用动图会更好理解, 有时间的话我会做一个动图出来, 现在大家脑补一下吧

## 关键点解析

- 动态规划

- 子问题

用dp[i] 来表示组成i块钱,需要最少的硬币数,那么

1. 第j个硬币我可以选择不拿       这个时候, 组成数 =  dp[i]

2. 第j个硬币我可以选择拿     这个时候, 组成数 =  dp[i - coins[j]] + dp[i]

- 和背包问题不同, 硬币是可以拿任意个

- 对于每一个 dp[i] 我们都选择遍历一遍 coin, 不断更新 dp[i]

eg: 

```js
   if (amount === 0) return 1;

    const dp = [Array(amount + 1).fill(1)];

    for (let i = 1; i < amount + 1; i++) {
      dp[i] = Array(coins.length + 1).fill(0);
      for (let j = 1; j < coins.length + 1; j++) { // 从1开始可以简化运算
        if (i - coins[j - 1] >= 0 ) { // 注意这里是coins[j -1]而不是coins[j]
          dp[i][j] = dp[i][j - 1] + dp[i - coins[j - 1]][j]; // 由于可以重复使用硬币所以这里是j不是j-1
        } else {
          dp[i][j] = dp[i][j - 1];
        }
      }
    }

    return dp[dp.length - 1][coins.length];

```


- 当我们选择一维数组去解的时候,内外循环将会对结果造成影响

![coin-change-2-wrong](../assets/problems/coin-change-2-wrong.png)

eg:

```js
  // 这种答案是不对的。
  // 原因在于比如amount = 5, coins = [1,2,5]
  // 这种算法会将[1,2,2] [2,1,2] [2, 2, 1] 算成不同的

    if (amount === 0) return 1;

    const dp = [1].concat(Array(amount).fill(0));

    for (let i = 1; i < amount + 1; i++) {
      for (let j = 0; j < coins.length; j++) {
        if (i - coins[j] >= 0) {
          dp[i] = dp[i] + dp[i - coins[j]];
        }
      }
    }

    return dp[dp.length - 1];

  // 正确的写法应该是内外循环调换一下, 具体可以看下方代码区

```

## 代码
```js
/*
 * @lc app=leetcode id=518 lang=javascript
 *
 * [518] Coin Change 2
 *
 * https://leetcode.com/problems/coin-change-2/description/
 *
 * algorithms
 * Medium (41.57%)
 * Total Accepted:    39.7K
 * Total Submissions: 94.6K
 * Testcase Example:  '5\n[1,2,5]'
 *
 * You are given coins of different denominations and a total amount of money.
 * Write a function to compute the number of combinations that make up that
 * amount. You may assume that you have infinite number of each kind of
 * coin.
 *
 *
 *
 *
 *
 *
 * Example 1:
 *
 *
 * Input: amount = 5, coins = [1, 2, 5]
 * Output: 4
 * Explanation: there are four ways to make up the amount:
 * 5=5
 * 5=2+2+1
 * 5=2+1+1+1
 * 5=1+1+1+1+1
 *
 *
 * Example 2:
 *
 *
 * Input: amount = 3, coins = [2]
 * Output: 0
 * Explanation: the amount of 3 cannot be made up just with coins of 2.
 *
 *
 * Example 3:
 *
 *
 * Input: amount = 10, coins = [10]
 * Output: 1
 *
 *
 *
 *
 * Note:
 *
 * You can assume that
 *
 *
 * 0 <= amount <= 5000
 * 1 <= coin <= 5000
 * the number of coins is less than 500
 * the answer is guaranteed to fit into signed 32-bit integer
 *
 *
 */
/**
 * @param {number} amount
 * @param {number[]} coins
 * @return {number}
 */
var change = function(amount, coins) {
  if (amount === 0) return 1;

  const dp = [1].concat(Array(amount).fill(0));

  for (let j = 0; j < coins.length; j++) {
    for (let i = 1; i < amount + 1; i++) {
      if (i - coins[j] >= 0) {
        dp[i] = dp[i] + dp[i - coins[j]];
      }
    }
  }

  return dp[dp.length - 1];
};
```

## 扩展

这是一道很简单描述的题目, 因此很多时候会被用到大公司的电面中。

相似问题:

[322.coin-change](./322.coin-change.md)