Files
codetutor/backend/data/questions/coin-change.yaml

126 lines
4.2 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.