Start learning free

LESSON 453 OF 540 · DYNAMIC PROGRAMMING

Coin Change II in Scala: Counting Combinations with 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

    SCALA · DYNAMIC PROGRAMMING

    Coin Change Ii in Scala, explained

    Problem overview

    The method change receives a target amount and an array coins of distinct coin values. It returns the number of combinations of coins that add up to exactly amount, or 0 if none do. 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, since 2s only make even sums. For amount = 0 it is 1: the combination with no coins at all.

    Approach: count sums, one coin at a time

    Listing every combination is hopeless: a few dozen coins can make over a billion combinations of 5000. The method counts them instead. Let ways(a) be the number of combinations that make the sum a, for every a from 0 to amount. The answer is ways(amount).

    The array 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 the sum a - coin, plus one more of this coin, is a combination for a. So ways(a) grows by ways(a - coin).

    Two choices in the loops decide what the numbers mean:

    • The sums go upward. When ways(a - coin) is read, it may already include the current coin, so the same coin can be used again and again.
    • The coins are the outer loop. While a coin is being added, the counts so far use only the coins before it. Every combination is therefore built in one fixed order: all its coins of the first value, then the second, and so on. That makes each combination counted exactly once. 1 + 3 is counted and 3 + 1 never is.

    Here is the array for amount = 5 and coins = [1, 2, 5] after each coin, for sums 0 to 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 1s there is one way to make every sum. Adding 2s makes 5 reachable as 2 + 2 + 1 and 2 + 1 + 1 + 1 besides 1 + 1 + 1 + 1 + 1, which is 3 ways. The 5 coin adds the single coin 5, for 4.

    Step by step

    1. Create ways as a val, a new Array[Int] of size amount + 1, so that every sum from 0 to amount has a slot. A new Array[Int] starts filled with zeros, which is right: before any coin is added, no positive sum can be made.
    2. Set ways(0) to 1. The sum 0 has exactly one combination, the empty one. Every other combination grows from it. Scala indexes arrays with parentheses.
    3. Loop over the coins with coin <- coins. This is the outer loop, which is what turns the count into combinations.
    4. Loop over the sums with a <- coin to amount. Sums 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: each entry reuses the counts for smaller sums.
    6. Finish with ways(amount) as the last expression of the method.

    The solution in Scala

    class Solution {
        def change(amount: Int, coins: Array[Int]): Int = {
            val ways = new Array[Int](amount + 1)
            ways(0) = 1
            for (coin <- coins) {
                for (a <- coin to amount) {
                    ways(a) += ways(a - coin)
                }
            }
            ways(amount)
        }
    }
    

    The Scala details worth a closer look:

    • coin to amount includes both ends. to builds an inclusive range, while until excludes the end. When coin is larger than amount the range is empty, for example (10 to 5).toList is List(), so a coin that is too big does nothing.
    • ways(a) += ... on an array element. += is shorthand for ways(a) = ways(a) + .... The array is a val, but its elements are mutable: the val only fixes which array the name refers to.
    • Parentheses, not brackets. Array access in Scala is ways(a), an ordinary method call. Square brackets are for type parameters, as in Array[Int].
    • The last expression is the result. The method ends with ways(amount) and no return. A for loop is a statement that gives no useful value, so the loops can sit above it.
    • Int is enough. It is a 32-bit integer that wraps silently on overflow, and the lesson guarantees that the answer, and the count for every smaller amount, fits.

    Complexity

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

    Space: O(amount). The array has amount + 1 counts, and the loops need only a few variables.

    Edge cases and common mistakes

    • Amount 0. The inner loops never run for a positive coin, since coin to 0 is empty, and the method returns ways(0), which is 1. change(0, Array(7)) gives 1.
    • No way to make the amount. change(3, Array(5)) gives 0: the coin is too big, the range is empty, and ways(3) stays 0. change(3, Array(2)) also gives 0.
    • A coin equal to the amount. change(10, Array(10)) gives 1, the single coin.
    • Swapping the loops. Putting the sums outside and the coins inside counts orders, not combinations. This version gives 7 for amount = 4 and coins = [1, 2, 3], where the correct answer is 4:
      for (a <- 1 to amount) {
          for (coin <- coins) {
              if (coin <= a) ways(a) += ways(a - coin)
          }
      }
      
      Now any coin can be the last one added to any sum, so 1 + 3 and 3 + 1 are both counted.
    • Looping the sums downward. That reads ways(a - coin) before the current coin has been added to it, so each coin can be used at most once.
    • Starting ways(0) at 0. Then nothing is ever added, and every answer is 0.

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