Chapter 59
HomeGiven coin denominations and a target amount, find the minimum number of coins needed to make the amount (or the number of ways to make it). This is unbounded knapsack — you can use each coin unlimited times.
dp[a] = minimum coins needed to make amount a. For each coin c,
try using it: dp[a] = min(dp[a], 1 + dp[a - c]).
int coin_change(vector& coins, int amount) { vector dp(amount + 1, INT_MAX / 2); dp[0] = 0; for (int a = 1; a <= amount; a++) { for (int c : coins) { if (a >= c) dp[a] = min(dp[a], 1 + dp[a - c]); } } return dp[amount] == INT_MAX / 2 ? -1 : dp[amount]; } // Forward iteration = unbounded (each coin unlimited) // Complexity: O(amount × |coins|)
fn coin_change(coins: &[i32], amount: i32) -> i32 {
let amount = amount as usize;
let mut dp = vec![i32::MAX / 2; amount + 1];
dp[0] = 0;
for a in 1..=amount {
for &c in coins {
if a >= c as usize {
dp[a] = dp[a].min(1 + dp[a - c as usize]);
}
}
}
if dp[amount] == i32::MAX / 2 { -1 } else { dp[amount] }
}
💡 Forward = unbounded, Backward = 0/1
Iterating amount forward (a = 1..amount) lets you reuse the same
coin multiple times — that's unbounded knapsack. Iterating backward
(a = amount..1) ensures each coin is used at most once — that's
0/1 knapsack. Same 1D array, different loop direction.
int count_ways(vector& coins, int amount) { vector dp(amount + 1, 0); dp[0] = 1; for (int c : coins) // ← coins outer! for (int a = c; a <= amount; a++) // ← forward (unbounded) dp[a] += dp[a - c]; return dp[amount]; } // Coins outer loop = combinations, not permutations. // If amount were outer, (1,2) and (2,1) would both count.
📐 Coin order matters — combinations vs permutations
With coins outer, each coin is considered once → combinations (1+2 and 2+1 are the same). With amount outer, every coin is reconsidered at each amount → permutations (1+2 and 2+1 are both counted). Know the difference!