Coin Change I


Link: https://leetcode.com/problems/coin-change/


Problem Statement

Given an integer array coins representing coins of different denominations and an integer amount, return the fewest number of coins that you need to make up that amount. If that amount cannot be made up, return -1.


Intuition

At each step, we can either:

We explore all valid paths and choose the one requiring the minimum number of coins.


1. Recursion (Top-Down)

IBH Format

Java Code

class Solution {
    public int coinChange(int[] coins, int amount) {
        int ans = helper(coins.length - 1, amount, coins);
        return (ans >= (int)1e9) ? -1 : ans;
    }

    private int helper(int i, int amount, int[] coins) {
        if (i == 0) {
            if (amount % coins[0] == 0) return amount / coins[0];
            else return (int)1e9;
        }

        int notPick = helper(i - 1, amount, coins);
        int pick = Integer.MAX_VALUE;
        if (coins[i] <= amount)
            pick = 1 + helper(i, amount - coins[i], coins);

        return Math.min(pick, notPick);
    }
}

Time Complexity


2. Memoization (Top-Down with Caching)

Java Code

class Solution {
    public int coinChange(int[] coins, int amount) {
        int n = coins.length;
        int[][] dp = new int[n][amount + 1];
        for (int[] row : dp) Arrays.fill(row, -1);
        int ans = helper(n - 1, amount, coins, dp);
        return (ans >= (int)1e9) ? -1 : ans;
    }

    private int helper(int i, int amount, int[] coins, int[][] dp) {
        if (i == 0) {
            if (amount % coins[0] == 0) return amount / coins[0];
            else return (int)1e9;
        }

        if (dp[i][amount] != -1) return dp[i][amount];

        int notPick = helper(i - 1, amount, coins, dp);
        int pick = Integer.MAX_VALUE;
        if (coins[i] <= amount)
            pick = 1 + helper(i, amount - coins[i], coins, dp);

        return dp[i][amount] = Math.min(pick, notPick);
    }
}

Time and Space Complexity


3. Tabulation (Bottom-Up)

Java Code

class Solution {
    public int coinChange(int[] coins, int amount) {
        int n = coins.length;
        int[][] dp = new int[n][amount + 1];

        for (int t = 0; t <= amount; t++) {
            if (t % coins[0] == 0) dp[0][t] = t / coins[0];
            else dp[0][t] = (int)1e9;
        }

        for (int i = 1; i < n; i++) {
            for (int t = 0; t <= amount; t++) {
                int notPick = dp[i - 1][t];
                int pick = (coins[i] <= t) ? 1 + dp[i][t - coins[i]] : (int)1e9;
                dp[i][t] = Math.min(pick, notPick);
            }
        }

        return dp[n - 1][amount] >= (int)1e9 ? -1 : dp[n - 1][amount];
    }
}

Time and Space Complexity


4. Space Optimization

Use two 1D arrays instead of a 2D table.

Java Code

class Solution {
    public int coinChange(int[] coins, int amount) {
        int n = coins.length;
        int[] prev = new int[amount + 1];

        for (int t = 0; t <= amount; t++) {
            if (t % coins[0] == 0) prev[t] = t / coins[0];
            else prev[t] = (int)1e9;
        }

        for (int i = 1; i < n; i++) {
            int[] curr = new int[amount + 1];
            for (int t = 0; t <= amount; t++) {
                int notPick = prev[t];
                int pick = (coins[i] <= t) ? 1 + curr[t - coins[i]] : (int)1e9;
                curr[t] = Math.min(pick, notPick);
            }
            prev = curr;
        }

        return prev[amount] >= (int)1e9 ? -1 : prev[amount];
    }
}

Time and Space Complexity


Choice Diagram

                   helper(i, t)
                  /            \
        Not Pick                  Pick
    helper(i-1, t)       1 + helper(i, t - coins[i])

Conclusion

The Coin Change problem is a classic unbounded knapsack variation. The recursion-based solution provides clarity, memoization adds efficiency, and space optimization makes the solution scalable for large inputs.