DEV Community

Cover image for Coin Change II: Counting Ways With Unlimited Coins
Nishant Gaurav
Nishant Gaurav

Posted on

Coin Change II: Counting Ways With Unlimited Coins

Rod Cutting asked: what's the maximum profit? Coin Change II asks: how many ways can you reach a target amount? The choice structure is the same. The transition direction is the same. Two things change: max() becomes +, and the initialisation needs one careful adjustment.

If you've been following the series, this chapter should feel like a small variation on something familiar rather than a new problem entirely.


The Problem

Given a list of coin denominations and a target amount, count the number of distinct combinations of coins that sum to that amount. Each coin can be used any number of times.

coins  = [1, 2, 5]
amount = 5

Ways to make 5:
  5
  2 + 2 + 1
  2 + 1 + 1 + 1
  1 + 1 + 1 + 1 + 1

Answer = 4
Enter fullscreen mode Exit fullscreen mode

Note that 2 + 2 + 1 and 1 + 2 + 2 count as the same combination. Order doesn't matter here — it's combinations, not permutations.


Where It Sits in the Pattern

The choice diagram is the standard TAKE / SKIP fork. And since the same coin can be used multiple times, it's Unbounded Knapsack. Since you're counting how many ways rather than finding a maximum, the operation is + instead of max().

Here's how the operation changes across the problems you've seen so far:

Problem Question Operation
0/1 Knapsack Maximum value max()
Subset Sum Possible or not or
Count of Subsets How many ways (each item once) +
Rod Cutting Maximum profit max()
Coin Change II How many ways (unlimited use) +

Coin Change II looks like Count of Subsets, but with one critical difference in the TAKE branch.


The Difference From Count of Subsets

Count of Subsets (from Chapter 4) also counted ways using +. But each element could only be used once, so TAKE moved to the previous row.

Coin Change II allows unlimited use, so TAKE stays on the same row.

Count of Subsets (0/1):
T[i][j] = T[i-1][j - arr[i-1]]   ← previous row
         + T[i-1][j]

Coin Change II (Unbounded):
T[i][j] = T[i][j - coins[i-1]]   ← same row
         + T[i-1][j]
Enter fullscreen mode Exit fullscreen mode

One index. Completely different behaviour.


The DP State

T[i][j] = number of ways to make amount j
           using the first i coins
Enter fullscreen mode Exit fullscreen mode

T[3][5] means: using only the first three coin denominations, how many combinations make 5?

Final answer: T[n][amount].


Initialisation

Two boundaries to think through.

No coins available (i = 0): You can't make any positive amount with zero coins. T[0][j] = 0 for all j > 0. This is already handled by initialising the table to zero.

Amount zero (j = 0): There is exactly one way to make amount zero: choose nothing. The empty combination. So T[i][0] = 1 for every i.

for i in range(n + 1):
    T[i][0] = 1
Enter fullscreen mode Exit fullscreen mode

This is different from Count of Subsets in Chapter 4, where you initialised only T[0][0] = 1 and let the transition handle zeros in the array. Here there are no zero-valued coins to worry about (coin denominations are always positive), so initialising the entire first column to 1 is safe and correct.


The Transition

When the coin fits (coins[i-1] <= j):

T[i][j] = T[i][j - coins[i-1]]   # TAKE: same row
         + T[i-1][j]              # SKIP: previous row
Enter fullscreen mode Exit fullscreen mode

We add because we're counting. Ways from TAKE plus ways from SKIP gives total ways.

When the coin doesn't fit (coins[i-1] > j):

T[i][j] = T[i-1][j]   # only SKIP
Enter fullscreen mode Exit fullscreen mode

The Complete Code

class Solution:

    def change(self, amount, coins):

        n = len(coins)

        # T[i][j] = number of ways to make amount j
        # using the first i coins
        T = [[0] * (amount + 1) for _ in range(n + 1)]

        # Amount 0 has exactly one combination: choose nothing
        for i in range(n + 1):
            T[i][0] = 1

        for i in range(1, n + 1):
            for j in range(1, amount + 1):

                if coins[i - 1] <= j:
                    # TAKE: same row (unbounded — coin can be used again)
                    # SKIP: previous row
                    # Add because we are counting ways
                    T[i][j] = (
                        T[i][j - coins[i - 1]]
                        + T[i - 1][j]
                    )
                else:
                    # Coin too large — only SKIP
                    T[i][j] = T[i - 1][j]

        return T[n][amount]
Enter fullscreen mode Exit fullscreen mode

A Dry Run

For coins = [1, 2, 5], amount = 5:

After filling the table with coin 1 (row 1), every amount from 1 to 5 has exactly 1 way (all ones). After adding coin 2 (row 2), each amount gains ways that combine 2s with 1s. By the time coin 5 (row 3) is added, T[3][5] captures all four combinations.

The key cell to trace: T[2][4] with coins [1, 2].

coins[1] = 2, j = 4
2 <= 4 → fits

T[2][4] = T[2][4-2] + T[1][4]
        = T[2][2]   + T[1][4]
Enter fullscreen mode Exit fullscreen mode

T[2][2] is at the same row, meaning: how many ways to make 2 using coins [1, 2]? That's 2 (either 2 or 1+1). T[1][4] is using only coin 1 to make 4: exactly 1 way (1+1+1+1). So T[2][4] = 2 + 1 = 3. Three ways to make 4 using coins [1, 2]: 2+2, 2+1+1, 1+1+1+1.


Three Things to Keep Straight

Why +? You're counting ways. Ways from TAKE plus ways from SKIP.

Why same row on TAKE? Unlimited coin use. Same coin available after picking it.

Why T[i][0] = 1? Amount zero has one valid combination: pick nothing.


Quick Revision

T[i][j] = number of ways to make amount j using first i coins

Initialisation:
  T[i][0] = 1 for all i     (empty combination makes 0)
  T[0][j] = 0 for all j > 0 (no coins, no ways)

Transition:
  if coins[i-1] <= j:
    T[i][j] = T[i][j-coins[i-1]] + T[i-1][j]
  else:
    T[i][j] = T[i-1][j]

Key: same row + + = Unbounded counting problem
Enter fullscreen mode Exit fullscreen mode

What You Now Understand

Coin Change II is Count of Subsets made unbounded. The + comes from counting. The same-row TAKE comes from unlimited use. The T[i][0] = 1 initialisation comes from the empty combination being a valid answer for amount zero.

Chapter 10 covers Coin Change I, where the question shifts again: instead of counting how many ways, you want the minimum number of coins. The transition flips from + to min(), and the initialisation needs a different starting value for the same reason Count of Subsets used None instead of False — the default value can't be a valid answer.

Top comments (0)