medium tree, graph, dp questions

This commit is contained in:
2025-04-28 23:04:27 +01:00
parent f8350bfdaf
commit 49c37548c0
4 changed files with 541 additions and 0 deletions

View File

@@ -0,0 +1,125 @@
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.