126 lines
4.2 KiB
YAML
126 lines
4.2 KiB
YAML
title: Coin Change
|
||
slug: coin-change
|
||
difficulty: medium
|
||
leetcode_id: 322
|
||
leetcode_url: https://leetcode.com/problems/coin-change/
|
||
categories:
|
||
- dynamic-programming
|
||
- arrays
|
||
patterns:
|
||
- dynamic-programming
|
||
|
||
description: |
|
||
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.
|
||
|
||
constraints: |
|
||
- 1 <= coins.length <= 12
|
||
- 1 <= coins[i] <= 2^31 - 1
|
||
- 0 <= amount <= 10^4
|
||
|
||
examples:
|
||
- input: "coins = [1,2,5], amount = 11"
|
||
output: "3"
|
||
explanation: "11 = 5 + 5 + 1"
|
||
- input: "coins = [2], amount = 3"
|
||
output: "-1"
|
||
explanation: "Cannot make amount 3 with only coin 2."
|
||
- input: "coins = [1], amount = 0"
|
||
output: "0"
|
||
explanation: "Amount 0 needs 0 coins."
|
||
|
||
explanation:
|
||
approach: |
|
||
1. Create a DP array where dp[i] = min coins needed for amount i
|
||
2. Initialize dp[0] = 0 (zero coins for zero amount)
|
||
3. For each amount from 1 to target, try each coin
|
||
4. If coin <= current amount, dp[i] = min(dp[i], dp[i - coin] + 1)
|
||
5. Return dp[amount] if valid, else -1
|
||
|
||
intuition: |
|
||
This is the classic unbounded knapsack problem. For each amount, we ask: "What's the
|
||
minimum coins needed if I use coin c as the last coin?"
|
||
|
||
If we use coin c last, we need 1 + dp[amount - c] coins. We try all possible "last coins"
|
||
and take the minimum. This optimal substructure makes it perfect for DP.
|
||
|
||
common_pitfalls:
|
||
- title: Wrong initialization
|
||
description: |
|
||
Initialize dp array to infinity (or amount + 1), not 0.
|
||
dp[0] = 0 is the only base case.
|
||
wrong_approach: "Initializing all dp values to 0"
|
||
correct_approach: "dp = [float('inf')] * (amount + 1); dp[0] = 0"
|
||
|
||
- title: Not checking if subproblem is solvable
|
||
description: |
|
||
Before using dp[i - coin], ensure i >= coin and dp[i - coin] is valid.
|
||
|
||
- title: Returning wrong value for impossible case
|
||
description: |
|
||
If dp[amount] is still infinity, return -1, not infinity.
|
||
|
||
key_takeaways:
|
||
- Classic unbounded knapsack problem
|
||
- Bottom-up DP builds solution from smaller amounts
|
||
- Try each coin as the "last coin" for each amount
|
||
- Greedy doesn't work here (counterexample: coins=[1,3,4], amount=6)
|
||
|
||
time_complexity: "O(amount × coins)"
|
||
space_complexity: "O(amount)"
|
||
complexity_explanation: |
|
||
Time: For each amount (1 to target), we try each coin.
|
||
Space: DP array of size amount + 1.
|
||
|
||
solutions:
|
||
- approach_name: Bottom-Up DP (Optimal)
|
||
is_optimal: true
|
||
code: |
|
||
def coin_change(coins: list[int], amount: int) -> int:
|
||
dp = [float('inf')] * (amount + 1)
|
||
dp[0] = 0
|
||
|
||
for i in range(1, amount + 1):
|
||
for coin in coins:
|
||
if coin <= i and dp[i - coin] != float('inf'):
|
||
dp[i] = min(dp[i], dp[i - coin] + 1)
|
||
|
||
return dp[amount] if dp[amount] != float('inf') else -1
|
||
explanation: |
|
||
Build up from amount 0. For each amount, try using each coin as the last coin.
|
||
Take the minimum of all valid options.
|
||
|
||
- approach_name: BFS (Alternative)
|
||
is_optimal: false
|
||
code: |
|
||
from collections import deque
|
||
|
||
def coin_change(coins: list[int], amount: int) -> int:
|
||
if amount == 0:
|
||
return 0
|
||
|
||
visited = {0}
|
||
queue = deque([(0, 0)]) # (current_sum, num_coins)
|
||
|
||
while queue:
|
||
current, num_coins = queue.popleft()
|
||
|
||
for coin in coins:
|
||
next_sum = current + coin
|
||
if next_sum == amount:
|
||
return num_coins + 1
|
||
if next_sum < amount and next_sum not in visited:
|
||
visited.add(next_sum)
|
||
queue.append((next_sum, num_coins + 1))
|
||
|
||
return -1
|
||
explanation: |
|
||
BFS finds shortest path in unweighted graph.
|
||
First time we reach 'amount' is the minimum coins.
|
||
Less space-efficient than DP for this problem.
|