Start learning free

LESSON 449 OF 536 · DYNAMIC PROGRAMMING

Coin Change II in Python: O(n · amount) Bottom-Up DP

Practice O(n · amount) Read the full explanation

You are given an integer amount, a total sum of money, and a list 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

    PYTHON · DYNAMIC PROGRAMMING

    Coin Change Ii in Python, explained

    Problem overview

    The function change(amount, coins) receives a target sum and a list of distinct coin values. Each value can be used as many times as you like. It returns how many different combinations of coins add up to exactly amount, or 0 if none does. A combination counts how many coins of each value it uses, not 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] the answer is 0, since coins of 2 only make even sums. When amount is 0 the answer is 1: the combination of no coins at all.

    Approach: count combinations for every smaller sum

    Listing every combination does not scale. With amount up to 5000 and up to 300 coin values, the number of combinations can be astronomically large, and it can't be produced one by one. The answer has to be counted, and counting works because the problem repeats itself: a combination for a sum a that ends with a coin of value c is a combination for a - c plus one more coin c.

    So build a list ways where ways[a] is the number of combinations that make the sum a, for every a from 0 to amount. It starts with ways[0] = 1 (no coins) and zeros everywhere else, and the answer is ways[amount]. The note asks for O(n · amount) time and O(amount) space, where n is the number of coins, and this list is exactly that size.

    Why the coins are the outer loop

    The order of the two loops decides what gets counted. With the coins in the outer loop, a coin is added to the counts only after all the earlier coins have been processed, so each combination is built in one fixed order: first its coins of the first value, then those of the second, and so on. Every combination is reached by exactly one path and counted once. 1 + 3 is counted; 3 + 1 never is.

    With the sums in the outer loop and the coins inside, any coin can be the last one added to any sum, so 1 + 3 and 3 + 1 are both counted. That counts ordered sequences, not combinations. For amount = 4 and coins = [1, 2, 3], the correct loop order gives 4 and the swapped one gives 7.

    Here is ways after each coin for amount = 5 and coins = [1, 2, 5]:

    after coin 1: [1, 1, 1, 1, 1, 1]
    after coin 2: [1, 1, 2, 2, 3, 3]
    after coin 5: [1, 1, 2, 2, 3, 4]
    

    With only the coin 1, every sum has one combination. Adding the coin 2 gives sum 4 three ways (1+1+1+1, 2+1+1, 2+2), and adding the coin 5 adds one more way to make 5. The last entry, 4, is the answer.

    Step by step

    1. Create ways as [0] * (amount + 1). Before any coin is used, no sum above 0 can be made, so every count starts at 0. The list has one slot for each sum from 0 to amount.
    2. Set ways[0] = 1. The sum 0 has one combination, the empty one. Every other combination grows from it: one coin added to no coins makes a sum equal to the coin's value.
    3. Loop over coins with a variable coin. This is the outer loop, which is what turns the counts into combinations rather than orders.
    4. Loop a over range(coin, amount + 1). A sum smaller than the coin can't contain it, so the loop starts at coin. It goes upward, which lets ways[a - coin] already include this coin, so one value can be used again and again. range stops before its end, so amount is the last sum. When coin is bigger than amount the range is empty and the coin is skipped.
    5. Add ways[a - coin] to ways[a]. This is the key line. Each combination for a - coin, plus one more of this coin, is a combination for a.
    6. Return ways[amount]. When amount is 0 neither loop does any work and the function returns ways[0], which is 1.

    The solution in Python

    class Solution:
        def change(self, amount: int, coins: List[int]) -> int:
            ways = [0] * (amount + 1)
            ways[0] = 1
            for coin in coins:
                for a in range(coin, amount + 1):
                    ways[a] += ways[a - coin]
            return ways[amount]
    

    Some Python details:

    • [0] * (amount + 1) builds a list of amount + 1 zeros. This is safe for numbers because integers are immutable: each slot holds its own value once you assign to it with ways[a] += .... (Repeating a list of lists this way would be a different story, because the rows would be one shared object.)
    • ways[a] += ways[a - coin] reads the right side first and then stores the sum. Because a goes up, ways[a - coin] was updated earlier in this same pass, and that is what allows repeated use of one coin.
    • Big counts need no care. Python integers don't overflow. The constraints promise that the answers fit in 32 bits anyway; for a larger input the code would still be correct.
    • The type hint List[int] is only documentation. It doesn't check or convert anything at run time.

    Complexity

    Each of the n coins sweeps over at most amount + 1 sums, with a constant amount of work per sum, so the time is O(n · amount). The list ways has amount + 1 entries, so the space is O(amount). The result for the largest amount is built entirely from the counts of smaller sums, so nothing is computed twice.

    Edge cases and common mistakes

    • Swapping the loops. The most common bug. The counts look plausible but are too big, since they count orders. For amount = 5 and coins = [1, 2, 5] the swapped loops return 9 instead of 4.
    • Forgetting ways[0] = 1. Every count stays 0 and the function always returns 0.
    • Amount 0. The answer is 1, not 0. With a single coin of 7, change(0, [7]) returns 1.
    • A coin larger than the amount. range(coin, amount + 1) is empty, so the coin changes nothing. change(10, [10]) returns 1, and a coin of 10 with amount 5 contributes nothing.
    • Starting the inner loop at 0. ways[a - coin] would then use a negative index, which in Python reads from the end of the list instead of failing.
    • Going downward in the inner loop. That would let each coin be used at most once, which solves a different problem.

    Testing

    These calls, made on a Solution object, give:

    change(5, [1, 2, 5])   -> 4
    change(3, [2])         -> 0
    change(4, [1, 2, 3])   -> 4
    change(0, [7])         -> 1
    change(10, [10])       -> 1
    change(5000, [1, 2, 5]) -> 1252001
    
    Keep going with Python500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free