Skip to content

Coding patternsBlind 75

Coin Change

Fill a table of the fewest coins for every amount from 0 up, trying each coin as the last one. O(amount × coins) time and O(amount) space, with a step-through, code in five languages and the Big O worked out from the code.

You are given a list of coin values coins and a target amount. You have as many coins of each value as you like. Return the fewest coins that add up to exactly amount, or -1 if no mix of coins does.

Example 1

Input: coins = [1, 3, 4], amount = 6

Output: 2

3 + 3 = 6. Taking the biggest coin first gives 4 + 1 + 1, which uses 3 coins.

coins
134

Example 2

Input: coins = [2], amount = 3

Output: -1

Twos only make even amounts.

Example 3

Input: coins = [5], amount = 0

Output: 0

Nothing to pay, so no coins.

  • 1 ≤ coins.length ≤ 12 (call it c)
  • 1 ≤ coins[i] ≤ 2³¹ − 1, all different
  • 0 ≤ amount ≤ 10,000 (call it A)
Can a coin be used more than once?
Yes, as many times as we like.
Do we return the coins or how many?
Only how many.
What if the amount is 0?
Return 0. No coins are needed.
Does taking the biggest coin first work?
No. Example 1 shows it giving 3 coins when 2 are enough.

Look at the last coin in the best answer. Whatever it is, the coins before it must be the best way to make the amount minus that coin. So the answer for an amount is built from answers for smaller amounts, and the same smaller amounts come up again and again. That is dynamic programming. Greedy fails, as Example 1 shows, so we really have to compare every coin.

There is no middle tab. Adding a memo to the recursion gives the same O(A × c) time as the table, with a deep call stack on top, so the table is shown instead.

Table, bottom up O(A × c)

There are only A + 1 different amounts the recursion can ask about: 0 to A. So answer each one once, from the bottom up, in a table.

fewest[0] = 0. Every other slot starts as “impossible”. For each amount sub_amount from 1 to A, try each coin as the last coin: if it fits, fewest[sub_amount - coin] + 1 is one way to make sub_amount. Keep the smallest. Since sub_amount - coin is smaller than sub_amount, its slot is already final.

In code, “impossible” is the number A + 1. No answer can need more than A coins, since every coin is at least 1, so A + 1 is safely too big and still adds without overflowing.

Why it is correct: the best way to make sub_amount ends with some coin, and the coins before it are the best way to make sub_amount - coin. The loop tries every last coin, and each fewest[sub_amount - coin] is already the best.

fewest
00∞1∞2∞3∞4∞5∞6
  1. fewest = [0] + [infinity] * amount
  2. for sub_amount in 1 .. amount:
  3. for coin in coins:
  4. if coin <= sub_amount:
  5. fewest[sub_amount] = min(fewest[sub_amount], fewest[sub_amount - coin] + 1)
  6. return fewest[amount], or -1 if it is infinity

Build a table from the bottom. fewest[sub_amount] is the fewest coins that make sub_amount. fewest[0] = 0, and the rest start as ∞, meaning "not possible yet". Coins: 1, 3, 4.

1 / 8
Solution.javasolution.pySolution.cssolution.gosolution.ts
class Solution {
// Table, bottom up: fewest[subAmount] is the fewest coins that make subAmount. Build it
// from 0 up, so every smaller amount is ready. O(amount × coins) time, O(amount) space.
public int coinChange(int[] coins, int amount) {
// amount + 1 coins is more than any real answer, so it works as "impossible".
int[] fewest = new int[amount + 1];
java.util.Arrays.fill(fewest, amount + 1);
fewest[0] = 0;
for (int subAmount = 1; subAmount <= amount; subAmount++) {
for (int coin : coins) {
// Use this coin last, on top of the best way to make subAmount - coin.
if (coin <= subAmount)
fewest[subAmount] = Math.min(fewest[subAmount], fewest[subAmount - coin] + 1);
}
}
return fewest[amount] <= amount ? fewest[amount] : -1;
}
}
Time O(A × c)Space O(A)Here is how we get these from the code, in four steps.
  1. 1 Find the work that repeats

    The inner loop body does one check, an addition and a min. That is a fixed job, so each run of the inner loop counts as 1 step.

    1. fewest = [0] + [infinity] * amount
    2. for sub_amount in 1 .. amount:
    3. for coin in coins:
    4. if coin <= sub_amount:
    5. fewest[sub_amount] = min(fewest[sub_amount], fewest[sub_amount - coin] + 1)
    6. return fewest[amount], or -1 if it is infinity
  2. 2 Count the runs on Example 1

    The table has one column per amount. Each amount tries all 3 coins, even the ones too big to fit.

    6 × 3 = 18 runs.

    sub_amount123456
    coins tried333333
    total
    6 × 3 = 18 inner runs
  3. 3 Write the count for any n

    The outer loop runs A times, and the inner loop runs c times for each:

    A × c runs

    Check it with Example 1: 6 × 3 = 18. That matches the count.

  4. 4 Keep only the part that grows fastest

    Nothing to drop. It is O(A × c): at most 10,000 × 12 = 120,000 steps.

    So the time is O(A × c).

  5. S Space: count the extra memory the same way

    The table has A + 1 slots. So the space is O(A).

    fewest table (amount + 1)

2,000 random inputs of 1 to 3 coins from 1 to 7 and an amount from 0 to 9. Amount 0 and cases with no answer, like even coins and an odd amount, come up. The plain recursion and the table gave the same answer every time.

Largest inputsResult
Amount 10,000 with 12 coins (the most allowed), answer 234 coins. Table, bottom up.0.01 s, and about 0.15 MB of extra memory: one list of 10,001 counts, O(A).
Amount 9,999 with 12 even coins: no answer, so the table never fills in. Table, bottom up.0.01 s, about 0.15 MB of extra memory, and −1, as it should be.