Coin Change
Problem
You are given an integer array coins
representing coins of different denominations and an integer amount
representing a total amount of money.
Return the fewest number of coins that you need to make up that amount. If that amount of money cannot be made up by any combination of the coins, return -1
.
You may assume that you have an infinite number of each kind of coin.
Example 1:
Input: coins = [1,2,5], amount = 11 Output: 3 Explanation: 11 = 5 + 5 + 1
Example 2:
Input: coins = [2], amount = 3 Output: -1
Example 3:
Input: coins = [1], amount = 0 Output: 0
Constraints:
1 <= coins.length <= 12
1 <= coins[i] <= 231 - 1
0 <= amount <= 104
Solution
/**
* @param {number[]} coins
* @param {number} amount
* @return {number}
*/
var coinChange = function(coins, amount) {
const dp = Array(amount + 1)
.fill(null)
.map(() => Array(coins.length + 1).fill(Infinity));
for (let i = 0; i <= coins.length; i++) {
dp[0][i] = 0;
}
for (let i = 1; i <= amount; i++) {
for (let j = 1; j <= coins.length; j++) {
if (coins[j - 1] > i) { // coin denomination > amount
dp[i][j] = dp[i][j - 1];
} else { // coin denomination <= amount
dp[i][j] = Math.min(dp[i][j - 1], 1 + dp[i - coins[j - 1]][j])
}
}
}
return dp[amount][coins.length] === Infinity ? -1 : dp[amount][coins.length];
};
We will implement a DP solution. Let dp[i][j]
be the min number of coins from coins[0:j]
to make up i
amount of money.
- For the base case:
- If
i = 0
, thendp[0][j] = Infinity
since usingcoins[0:0]
(ie. no coins) cannot make up any amount of money (note we useInfinity
to represent impossible). - If
j = 0
, thendp[i][0] = 0
since0
amount of money can be made up using0
coins.
- If
- For the recursive case, if the current coin
coin[j - 1]
has a greater denomination value than the amount we are trying to make, there's no way to use this coin, so we use the min number of coins without including this coin (ie.dp[i][j - 1]
). Otherwise, we take the min of either not including the current coin (ie.dp[i][j - 1]
) or using the current coin (ie.1 + dp[i - coins[j - 1]][j]
).
Optimization
We can optimize the above solution to use O(amount)
space as follows:
/**
* @param {number[]} coins
* @param {number} amount
* @return {number}
*/
var coinChange = function(coins, amount) {
const dp = Array(amount + 1).fill(Infinity)
dp[0] = 0;
for (const coin of coins) {
for (let i = 1; i <= amount; i++) {
if (coin <= i) {
dp[i] = Math.min(dp[i], 1 + dp[i - coin])
}
}
}
return dp[amount] === Infinity ? -1 : dp[amount];
};
Notice that from the 2D DP solution, we fill in dp
from left to right (row by row), where the next value of the row is only dependent on the direct previous value of the same row and the (not necessarily direct) previous value of the current col. Thus, the direct previous value of each row needs to be stored in dp
; and since dp
is evaluated col by col, the lower indexed values in dp
are all relative to the current col. Hence we can reduce dp
down to a 1D array of size amount + 1
.
Question
Does above solution still work even if we swap the order of the two loops?
Answer
This destroys the property that one col is evaluated completely before moving on to the next (ie. lose previous value of the current col as we now only have the final previous col values of dp
when evaluating the next row). That said, in this problem, the ordering does not actually matter (see explanation below), and so the solution will still work.
First, observe that each row of dp
is non-decreasing due to the min
operator, so taking the min of the smallest value before is the same as later (eg. min(min(9, 10), 1) = (min(9, 1), 10) = 1
, but the intermediate value of min(9, 10) != min(9, 1)
).
In our 2D version, eventually we will be taking the min of the previous value in the row and the last col value when our loop reaches j = coins.length
. Thus, it won't effect the final solution even if we use 1 + dp[i - coins[j - 1]][coins.length]
instead of 1 + dp[i - coins[j - 1]][j]
(since the last value in any row of dp
is always the smallest value of that entire row). That said, all values of dp
that are not in the last col will be messed up (correct last col value implies correct final result).