150NeetCode 150

114.Coin Change II

Medium2-D Dynamic Programming · 4/11

Problem

You are given an integer amount and an array coins of distinct denominations. You have an unlimited supply of each coin.

Return the number of combinations of coins that add up to exactly amount. Order does not matter — 1+2 and 2+1 are the same combination. If the amount cannot be made, return 0. The answer fits in a signed 32-bit integer.

Examples

Input:  amount = 5, coins = [1, 2, 5]
Output: 4
Explanation: 5 = 5
             5 = 2 + 2 + 1
             5 = 2 + 1 + 1 + 1
             5 = 1 + 1 + 1 + 1 + 1
Input:  amount = 3, coins = [2]
Output: 0
Explanation: Only even amounts can be made with 2s.
Input:  amount = 10, coins = [10]
Output: 1

Constraints

  • 1 <= coins.length <= 300
  • 1 <= coins[i] <= 5000
  • All values of coins are unique.
  • 0 <= amount <= 5000

💡 Key Insight

To count combinations (not permutations), fix an order on the coins and decide coin by coin: for coin i, either use it once more (stay on coin i, amount shrinks) or move on to coin i + 1 forever. That gives ways(i, a) = ways(i, a - coins[i]) + ways(i + 1, a) — the unbounded knapsack. In table form, with the coin loop outside the amount loop, a single 1-D array dp[a] += dp[a - coin] does it.

Recursion (Brute Force)

TimeExponentialSpaceO(amount / minCoin + n)

Intuition

count(i, a) = number of ways to make amount a using only coins i, i+1, .... Because we never go back to an earlier coin, each multiset of coins is generated exactly once.

Algorithm

  1. count(i, a):
    • If a == 0, return 1 (found one combination).
    • If a < 0 or i == coins.length, return 0.
    • Return count(i, a - coins[i]) + count(i + 1, a).
  2. Return count(0, amount).

Visualization

flowchart TD
    A["count(0, 3)<br/>coins [1,2]"] -->|"use 1"| B["count(0, 2)"]
    A -->|"skip 1"| C["count(1, 3)"]
    B -->|"use 1"| D["count(0, 1)"]
    B -->|"skip 1"| E["count(1, 2)"]
    D -->|"use 1"| F["count(0, 0) = 1<br/>1+1+1"]
    D -->|"skip 1"| G["count(1, 1) = 0"]
    E -->|"use 2"| H["count(1, 0) = 1<br/>1+2"]
    C -->|"use 2"| I["count(1, 1) = 0"]
    C -->|"skip 2"| J["count(2, 3) = 0"]

Total = 2 combinations (1+1+1, 1+2); 2+1 is never generated because after skipping coin 1 we can't return to it.

Code

java
class Solution {
    public int change(int amount, int[] coins) {
        return count(coins, 0, amount);
    }

    private int count(int[] coins, int i, int a) {
        if (a == 0) {
            return 1; // exact amount reached
        }
        if (a < 0 || i == coins.length) {
            return 0; // overshot or no coins left
        }
        // take coin i again, or stop using coin i
        return count(coins, i, a - coins[i]) + count(coins, i + 1, a);
    }
}

Complexity

  • Time: Exponential — roughly O(2^(amount / minCoin + n)) in the worst case.
  • Space: O(amount / minCoin + n) — recursion depth.

Complexity Comparison

Approach Time Space
Recursion (Brute Force) Exponential O(amount + n)
Top-Down Memoization O(n · amount) O(n · amount)
Bottom-Up 2-D Tabulation O(n · amount) O(n · amount)
Space-Optimized 1-D DP O(n · amount) O(amount)

Common Pitfalls

  • Putting the amount loop outside and the coin loop inside — that counts permutations (1+2 and 2+1 separately), which is Combination Sum IV, not this problem.
  • Iterating a downward in the 1-D version — that turns it into 0/1 knapsack (each coin used at most once).
  • Forgetting dp[0] = 1 — with it at 0, everything stays 0. Note amount = 0 must return 1.
  • Confusing this with Coin Change (I), which asks for the minimum number of coins (min, not +).
  • Intermediate counts for unreachable-prefix combos can exceed int on adversarial inputs; Java int addition wraps consistently, so the final (guaranteed 32-bit) answer is still correct — but don't use -1 as a memo sentinel; use Integer null.