322. Coin Change
Description
Difficulty: Medium
You are given an integer array coins representing coins of different denominations and an integer amount representing a total amount of money.
Return the fewest number of coins that you need to make up that amount. If that amount of money cannot be made up by any combination of the coins, return -1.
You may assume that you have an infinite number of each kind of coin.
Example 1:
Input: coins = [1,2,5], amount = 11
Output: 3
Explanation: 11 = 5 + 5 + 1
Example 2:
Input: coins = [2], amount = 3
Output: -1
Example 3:
Input: coins = [1], amount = 0
Output: 0
Constraints:
- 1 <= coins.length <= 12
- 1 <= coins[i] <= 231 - 1
- 0 <= amount <= 104
Solution
The idea behind this solution is Dynamic Programming (DP), specifically a bottom-up approach (also known as tabulation). The essence of this technique is to solve a complex problem by breaking it down into smaller, overlapping subproblems and solving each subproblem only once, storing its result.
Here's how that idea applies to Coin Change:
Breaking Down the Problem: Instead of directly figuring out the minimum coins for the
amount, we ask: "What's the minimum coins needed for1? Then for2? ... up toamount?" Eachdp[i]represents the answer to the subproblem of making change for amounti.Building Up the Solution (Bottom-Up): We start with the simplest subproblem (
dp[0] = 0, no coins for no amount). Then, for each subsequent amounti, we consider all possiblecoinswe could use to reachi. If we use acoin, the problem reduces to finding the minimum coins fori - coin. Since we've already calculateddp[i - coin](becausei - coinis smaller thani), we just add1(for the currentcoin) to that known minimum.Optimization/Memoization (Implicit): By storing the results for each
dp[i]and reusing them, we avoid redundant calculations. For example, if bothamount - coinAandamount - coinBdepend on the minimum coins forX,dp[X]is computed only once. This is why DP is far more efficient than naive recursion for problems with overlapping subproblems. TheMath.minoperation ensures we always pick the optimal (minimum) path to reachdp[i].
This pattern of building up solutions from smaller, known subproblems to larger, unknown ones is fundamental to dynamic programming.
Code
import java.util.Arrays;
class Solution {
public int coinChange(int[] coins, int amount) {
int[] dp = new int[amount + 1];
Arrays.fill(dp, amount + 1);
dp[0] = 0;
for (int i = 1; i <= amount; ++i) {
for (int coin : coins) {
if (coin <= i) {
dp[i] = Math.min(dp[i], dp[i - coin] + 1);
}
}
}
return dp[amount] > amount ? -1 : dp[amount];
}
}Time Complexity: O(amount * num_coins)
The core idea involves iterating through every possible amount from 1 to amount and, for each amount, considering every available coin. This directly translates to a nested loop structure, resulting in a linear dependency on both the target amount and the coins array's length.
Space Complexity: O(amount)
We use a dp array whose size directly scales with the amount. This array stores the minimum coin counts for all sub-amounts from 0 up to the target amount, enabling efficient lookups and avoiding recomputation.
