Start learning free

LESSON 448 OF 535 · DYNAMIC PROGRAMMING

Coin Change II in C++: Counting Combinations with DP

Practice O(n · amount) Read the full explanation

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

    C++ · DYNAMIC PROGRAMMING

    Coin Change Ii in C++, explained

    Problem overview

    The function change receives a target amount and a vector coins of distinct coin values. It returns how many different combinations of coins add up to exactly amount, using each value as many times as you like. 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 one. If nothing adds up, the answer is 0, and for amount = 0 the answer is 1: the combination of no coins at all.

    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.

    Approach: count sums, one coin at a time

    Listing every combination is hopeless: the number of combinations grows so fast that a few dozen coins can make more than a billion ways to reach 5000. The statement asks for O(n * amount) time, where n is the number of coins, so the answer has to be counted, not listed.

    Let ways[a] be the number of combinations that make the sum a, for every a from 0 to amount. The table starts with ways[0] = 1 (the empty combination) and zeros elsewhere. Then the coins are added 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 ways[a] grows by ways[a - coin].

    Two details make this count combinations and not something else:

    • The sums go upward. When a is processed, ways[a - coin] has already been updated for this coin, so it already includes combinations that use the coin once or more. That lets one coin value be used again and again.
    • The coins are the outer loop. While a coin is being added, the counts only use the coins before it. So every combination is built in one fixed order: all its coins of the first value, then of the second, and so on. It is counted exactly once. 1 + 3 is built, and 3 + 1 never is.

    Swapping the loops, with the sums outside and the coins inside, lets any coin be the last one added to any sum, so 1 + 3 and 3 + 1 are both counted. That counts orders, not combinations: for amount = 4 and coins = [1, 2, 3] it gives 7 instead of 4.

    Here is the table for amount = 5 and coins = [1, 2, 5], one row after each coin, for sums 0 to 5:

    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 entry, 4, is the answer. After coin 1, every sum has one way: all ones. Coin 2 adds the combinations with at least one 2, and coin 5 adds the single combination 5 to the last entry.

    Step by step

    1. Create ways as a vector<int> of size amount + 1 with every element 0. The constructor takes the size first and then the value of every element. Before any coin is added, no sum above 0 can be made.
    2. Set ways[0] to 1. The sum 0 has exactly one combination, no coins. Every other combination grows from it.
    3. Loop over coins with a range-based for and an int coin. This is the outer loop, which makes the counts combinations.
    4. Loop a from coin up to amount. A sum smaller than the coin cannot contain it. Going up lets a smaller sum already include this coin.
    5. Add ways[a - coin] to ways[a] with +=. This is the key line, and it reuses the counts for smaller sums: that is the dynamic programming.
    6. Return ways[amount] after the outer loop. For amount = 0 the inner loops never run and this returns ways[0], which is 1.

    The solution in C++

    class Solution {
    public:
        int change(int amount, vector<int>& coins) {
            vector<int> ways(amount + 1, 0);
            ways[0] = 1;
            for (int coin : coins) {
                for (int a = coin; a <= amount; a++) {
                    ways[a] += ways[a - coin];
                }
            }
            return ways[amount];
        }
    };
    

    The C++ details worth a closer look:

    • vector<int> ways(amount + 1, 0) is the constructor with a size and a fill value. Without the second argument the elements would be value-initialized to 0 as well, but writing the 0 makes the intent clear.
    • for (int coin : coins) copies each coin into a local int, which is cheap for an int. The parameter coins is a vector<int>&, a reference, so the whole vector is not copied when the function is called.
    • Counts and int. The counts are plain int values. The statement promises that the answer, and the number of combinations for every smaller amount, fits in 32 bits, so += never overflows. Signed overflow would be undefined behaviour in C++, not a silent wrap.
    • Coin order doesn't matter. With coins = [3, 1, 2] and amount = 4, the function also returns 4. Any order gives the same set of combinations, only built in a different sequence.

    Complexity

    Time: O(n * amount). Each of the n coins runs the inner loop over at most amount + 1 sums, with one addition per step.

    Space: O(amount). The only extra memory is the ways vector with amount + 1 entries. There is no two-dimensional table: each coin updates the same vector in place.

    Edge cases and common mistakes

    • Amount 0. The answer is 1, not 0. For amount = 0 and coins = [7] the function returns 1.
    • Impossible amounts. amount = 3 with coins = [2] returns 0, because the entries for odd sums are never touched.
    • A coin larger than the amount. The inner loop starts above amount and does nothing, so the coin is ignored.
    • Swapped loops. Putting the sums in the outer loop counts orders. For amount = 4 and coins = [1, 2, 3] it returns 7 instead of 4.
    • Going down in the inner loop. Looping a from amount down to coin reads values that do not yet include the current coin, so each coin is used at most once. That solves a different problem.
    • Forgetting ways[0] = 1. Every entry stays 0 and the function returns 0 for every input.

    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 = [5] gives 1.
    Keep going with C++500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free