Start learning free

LESSON 453 OF 540 · DYNAMIC PROGRAMMING

Coin Change II in TypeScript: 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

    TYPESCRIPT · DYNAMIC PROGRAMMING

    Coin Change Ii in TypeScript, explained

    Problem overview

    The method change receives a target amount and an array coins of different coin values. It returns how many combinations of coins add up to exactly amount, or 0 if none does. Each coin value can be used any number of times. 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] it is 0, because 2s only make even sums. When amount is 0 the answer is 1: the combination of no coins at all.

    Approach: count the ways for every smaller sum

    Listing every combination is hopeless: with a few dozen coins and an amount of 5000 there can be more than a billion of them. The statement asks for O(n · amount) time and O(amount) space, where n is the number of coins. That calls for counting instead of listing.

    Let ways[a] be the number of combinations that make the sum a, for every a from 0 to amount. Before any coin is used, only the sum 0 can be made (with no coins), so ways[0] is 1 and the rest are 0. The answer will be ways[amount].

    Now add the coins one at a time. When a coin is added, every combination for a - coin, plus one more of this coin, is a combination for a. So for each sum a from coin up to amount, add ways[a - coin] to ways[a]. Going upward matters: ways[a - coin] may already include this coin, which is what lets one coin value be used again and again.

    Here is the array for amount = 5 and coins = [1, 2, 5], from sum 0 to sum 5, 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
    

    The last number is the answer, 4.

    Why the coins are the outer loop

    While a coin is being added, the counts so far use only the coins before it. So each combination is built in one fixed order: first all its coins of the first value, then all of the second, and so on. It is therefore counted exactly once, and 1 + 3 is counted while 3 + 1 never is.

    With the sums outside and the coins inside, any coin could be the last one added to any sum, and 1 + 3 and 3 + 1 would both be counted. That counts orders, not combinations: for amount = 5 and coins = [1, 2, 5] it returns 9 instead of 4.

    Step by step

    1. Create ways as a number[] with amount + 1 zeros, using new Array(amount + 1).fill(0). Index a counts the combinations that make a.
    2. Set ways[0] to 1. The sum 0 has exactly one combination, and every other combination grows from it.
    3. Loop over coins with for...of. The coins are the outer loop, which is what makes the counts combinations.
    4. Loop over the sums with a from coin up to and including amount. A sum smaller than the coin can't contain it, so it is skipped.
    5. Add ways[a - coin] to ways[a]. This is the key line: each entry reuses counts for smaller sums.
    6. Return ways[amount]. For amount = 0 the inner loops never run, and this returns ways[0], which is 1.

    The solution in TypeScript

    class Solution {
        change(amount: number, coins: number[]): number {
            const ways: number[] = new Array(amount + 1).fill(0);
            ways[0] = 1;
            for (const coin of coins) {
                for (let a = coin; a <= amount; a++) {
                    ways[a] += ways[a - coin];
                }
            }
            return ways[amount];
        }
    }
    

    A few TypeScript details:

    • new Array(n).fill(0). new Array(amount + 1) alone makes an array of empty slots, and fill(0) puts a 0 in each. The annotation number[] on the constant is only a compile-time check: it is erased when the code becomes JavaScript.
    • const still allows changes inside. ways is a const, so the name can't be assigned to another array, but its elements can change, and ways[a] += ... does that.
    • Sharing one object. fill puts the same value in every slot. That is harmless for the number 0, but new Array(3).fill([]) puts the same array in all three slots, so pushing to one shows up in all of them. For a table of rows, build each row separately.
    • Big counts. A number is a double, exact for integers up to 2^53. The constraints promise that the answer and every count for a smaller amount fit in 32 bits, so the additions are exact.

    Complexity

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

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

    Edge cases and common mistakes

    • Swapping the loops. Putting the sums outside and the coins inside counts orders. For amount = 4 and coins = [1, 2, 3] that version returns 7, while the correct answer is 4.
    • Amount 0. The answer is 1, not 0. That follows from ways[0] = 1: the empty combination.
    • Unreachable sums. For amount = 3 and coins = [2], ways[3] never receives anything and stays 0. No special case is needed.
    • A coin larger than the amount. change(10, [10]) returns 1, while a coin larger than the amount makes the inner loop's starting value exceed amount, so it never runs and contributes nothing.
    • Going downward. Looping a from amount down to coin would let each coin be used at most once, which answers a different problem.

    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.
    Keep going with TypeScript500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free