Start learning free

LESSON 453 OF 540 · DYNAMIC PROGRAMMING

Coin Change II in Go: O(n · amount) Dynamic Programming

Practice O(n · amount) Read the full explanation

You are given an integer amount, a total sum of money, and an array coins of different coin values.

Return the number of combinations of coins that add up to exactly amount. If no combination of the coins adds up to amount, return 0.

You may use each coin value as many times as you want. A combination counts how many coins of each value it uses, not their order: 2 + 2 + 1 and 1 + 2 + 2 are the same combination.

 

Example 1:

Input: amount = 5, coins = [1, 2, 5]
Output: 4
Explanation: There are four combinations:
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: Coins of 2 only make even sums, so 3 can't be made.

Example 3:

Input: amount = 4, coins = [1, 2, 3]
Output: 4
Explanation: 4 = 1 + 1 + 1 + 1 = 2 + 1 + 1 = 2 + 2 = 3 + 1. Counting different orders as different, such as 3 + 1 and 1 + 3, would give 7 instead.

 

Constraints:

  • 1 <= coins.length <= 300
  • 1 <= coins[i] <= 5000
  • All the values in coins are different.
  • 0 <= amount <= 5000 (when amount is 0, the answer is 1: the combination of no coins at all)
  • The answer fits in a 32-bit signed integer, and so does the number of combinations for every smaller amount.

Note:

Your algorithm should run in O(n · amount) time and O(amount) space, where n is the number of coins.



Listing every combination is far too slow: a few dozen coins can make more than a billion combinations of 5000. Count them instead: let ways[a] be the number of combinations that make the sum a, for every a from 0 to amount. Start with ways[0] = 1, the combination of no coins, and every other entry at 0. The answer is ways[amount].

Add the coins one at a time, in an outer loop. For each coin, go over the sums a from coin up to amount and add ways[a - coin] to ways[a]: every combination for a - coin, plus one more of this coin, is a combination for a. Going up lets ways[a - coin] already contain this coin, so it can be used many times.
Why the coins must be the outer loop: when a coin is added, the counts so far only use the coins before it. So every combination is built in one fixed order, first all its coins of the first value, then all of the second, and so on, and it is counted exactly once: 1 + 3 is counted, 3 + 1 never is. With the sums outside and the coins inside, any coin could be the last one added to any sum, so 1 + 3 and 3 + 1 would both be counted: that counts orders, not combinations.

Each of the n coins goes over at most amount + 1 sums, with a constant amount of work for each: O(n · amount) time. ways holds amount + 1 counts: O(amount) space.
STEP 1 OF 6
    Ctrl + Enter to check

    GO · DYNAMIC PROGRAMMING

    Coin Change Ii in Go, explained

    Problem overview

    The function change receives a target amount and a slice coins of distinct coin values. It returns how many different combinations of coins add up to exactly amount, or 0 if none do. Each coin value can be used as many times as you like. A combination is defined by how many coins of each value it uses, not by their order, so 2 + 2 + 1 and 1 + 2 + 2 are the same combination.

    For amount = 5 and coins = [1, 2, 5] the answer is 4: 5, 2 + 2 + 1, 2 + 1 + 1 + 1 and 1 + 1 + 1 + 1 + 1. For amount = 3 and coins = [2] it is 0, because coins of 2 only make even sums. When amount is 0 the answer is 1, the combination that uses no coins.

    Approach: count sums instead of listing combinations

    Listing every combination is hopeless for large inputs: a few dozen coin values can make over a billion combinations of 5000. Counting them is enough, and counting fits dynamic programming.

    Let ways[a] be the number of combinations that make the sum a, for every a from 0 to amount. At the start only ways[0] is 1 (the empty combination), and everything else is 0. The answer is ways[amount].

    Now add the coins one at a time. When a coin is added, every combination for a - coin, plus one more of that coin, is a combination for a. So ways[a] grows by ways[a - coin]. Going through the sums from small to large lets ways[a - coin] already include the current coin, so the same coin value can be used again and again.

    The order of the two loops decides what is counted. With the coins in the outer loop, the counts so far only use the coins before the current one. Each combination is therefore built in one fixed order: all its coins of the first value, then all of the second, and so on. Each one is counted exactly once, as 1 + 3 but never as 3 + 1. If the sums were the outer loop and the coins the inner one, any coin could be the last one added to any sum, and both orders would count. The result would be the number of ordered sequences. For amount = 4 and coins = [1, 2, 3] that version returns 7 instead of 4.

    The cost is O(n · amount) time, where n is the number of coins, and O(amount) space for the slice.

    Step by step

    1. Create ways with make([]int, amount+1). It needs one slot per sum from 0 to amount, and make fills it with zeros, which is right because no positive sum can be made before any coin is added.
    2. Set ways[0] = 1. The sum 0 has exactly one combination: no coins. Every other combination grows from this one.
    3. Loop over the coins with range, dropping the index with _. This is the outer loop, which makes the counts combinations rather than orders.
    4. Loop over the sums from coin up to amount. A sum smaller than the coin can't contain it, and going upward lets a smaller sum already include this coin.
    5. Add ways[a-coin] to ways[a]. This is the key line: each count is built from the counts of smaller sums.
    6. Return ways[amount] after the outer loop. If amount is 0, the inner loop never runs and this returns ways[0], which is 1.

    The solution in Go

    func change(amount int, coins []int) int {
        ways := make([]int, amount+1)
        ways[0] = 1
        for _, coin := range coins {
            for a := coin; a <= amount; a++ {
                ways[a] += ways[a-coin]
            }
        }
        return ways[amount]
    }
    

    Here is how ways changes for amount = 5 and coins = [1, 2, 5], from a run of this code with a print after each coin:

    start  [1 0 0 0 0 0]
    coin 1 [1 1 1 1 1 1]
    coin 2 [1 1 2 2 3 3]
    coin 5 [1 1 2 2 3 4]
    

    After the coin 1, every sum has exactly one combination (all ones). The coin 2 adds the ones that use 2s: sum 4 now has three (1+1+1+1, 2+1+1, 2+2). The coin 5 adds the single combination 5 to the last slot, giving 4.

    The Go details worth a closer look:

    • make and zero values. make([]int, n) creates a slice of length n with every element 0, so only ways[0] needs setting.
    • range over a slice yields the index and the value. Go rejects an unused variable, so the index is written as _.
    • Three-part for. Go has no while; for a := coin; a <= amount; a++ is the counted loop, and a exists only inside it.
    • += reads and writes the same slice. ways[a] += ways[a-coin] reads an entry that may have been updated earlier in this same pass. That is exactly the "use the coin again" behaviour, not a bug.
    • int is 64 bits in Go, so the counts fit easily. The lesson promises answers within 32 bits anyway. With amount = 5000 and coins = [1, 2, 5] the function returns 1252001.

    Complexity

    Time: O(n · amount). Each of the n coins goes over at most amount + 1 sums, with constant work for each.

    Space: O(amount). The slice ways holds amount + 1 counts, and nothing else grows with the input.

    Edge cases and common mistakes

    • Swapped loops. Putting the sums outside and the coins inside counts orders. For amount = 4 and coins = [1, 2, 3] that returns 7 instead of 4.
    • Forgetting ways[0] = 1. Every count then stays 0, and change(5, []int{1, 2, 5}) returns 0 instead of 4.
    • Amount 0. The answer is 1, whatever the coins are. change(0, []int{7}) returns 1, and so does an empty coins slice.
    • A coin larger than the amount. change(10, []int{10}) returns 1, and a coin bigger than amount simply doesn't enter the inner loop, because the starting value of a already exceeds amount.
    • Unreachable sums. change(3, []int{2}) returns 0: no coin ever adds to an odd index, so ways[3] stays 0.
    • Looping the sums downward. A descending inner loop would let each coin be used at most once, which solves a different problem. The lesson needs the upward direction.

    Testing

    • amount = 5, coins = [1, 2, 5] gives 4.
    • amount = 3, coins = [2] gives 0.
    • amount = 4, coins = [1, 2, 3] gives 4.
    • amount = 0, coins = [7] gives 1.
    • amount = 10, coins = [10] gives 1.
    • amount = 5000, coins = [1, 2, 5] gives 1252001.
    Keep going with Go500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free