Coin Change und Treppe mit minimalen Kosten
Formulieren Sie die Rekurrenzen für coin-change und min-cost-climbing-stairs, wählen Sie die richtige DP-Richtung und verfolgen Sie die Tabelle manuell.
Coin Change und Treppe mit minimalen Kosten ist eine kostenlose Coding Interview Prep-Lektion auf CoddyKit. Dies ist Lektion 4 von 4. Du kannst die komplette Lektion unten kostenlos lesen – dann übst du sie direkt im Browser mit einem integrierten Code-Editor und einem KI-Tutor rund um die Uhr. Sie ist Teil des Coding Interview Prep-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der Coding Interview Prep-Kurs umfasst insgesamt 4 Lektionen.
Coin Change: Das Problem
Coin Change (LeetCode #322) gibt Ihnen Münzwerte und einen Zielbetrag vor. Finden Sie die minimale Anzahl an Münzen, die benötigt wird, um genau diesen Betrag zu bilden. Von jedem Münzwert stehen unbegrenzt viele Münzen zur Verfügung. Dies ist eine klassische Variante des unbeschränkten Rucksackproblems — jedes Element, also jede Münze, kann beliebig oft verwendet werden. Das Problem gehört zu den wichtigsten DP-Problemen, da es Ihre Fähigkeit testet, eine Rekurrenz von Grund auf zu formulieren.
# Problem examples:
# coins=[1,5,6,9], amount=11 -> 2 (5+6 or 2+9? no: 5+6=11 YES)
# coins=[2], amount=3 -> -1 (impossible)
# coins=[1,2,5], amount=11 -> 3 (5+5+1)
# coins=[186,419,83,408], amount=6249 -> 20
# Key choices:
# - Try each coin denomination at each step
# - Minimum coins = 1 + minimum(coins to make amount - coin)
# - If amount < 0: impossible
# - If amount = 0: done (0 coins)
print('Coin change: unbounded knapsack, find minimum count')Coin Change: Herleitung der Rekurrenz
Definieren Sie dp[i] als die minimale Anzahl an Münzen, mit der sich der Betrag i bilden lässt. Probieren Sie für jeden Betrag i jede Münze c aus: Falls i >= c gilt, dann ist dp[i] = min(dp[i], 1 + dp[i-c]). Die „1“ steht für die gerade verwendete Münze; dp[i-c] ist die optimale Lösung für den verbleibenden Betrag. Dabei wird davon ausgegangen, dass unendlich viele Münzen zur Verfügung stehen. Basisfall: dp[0] = 0. Initialisieren Sie alle anderen Einträge mit Unendlich, um darzustellen, dass sie „noch nicht erreichbar“ sind.
def coin_change(coins, amount):
# dp[i] = min coins to make amount i
dp = [float('inf')] * (amount + 1)
dp[0] = 0 # base: 0 coins for amount 0
for i in range(1, amount + 1):
for coin in coins:
if i >= coin and dp[i - coin] != float('inf'):
dp[i] = min(dp[i], 1 + dp[i - coin])
return dp[amount] if dp[amount] != float('inf') else -1
print(coin_change([1, 5, 6, 9], 11)) # 2
print(coin_change([2], 3)) # -1
print(coin_change([1, 2, 5], 11)) # 3
# Trace dp for coins=[1,5] amount=6:
# dp[0]=0, dp[1]=1, dp[2]=2, dp[3]=3, dp[4]=4, dp[5]=1, dp[6]=2Coin Change: Warum Greedy scheitert
Greedy, also immer die größte passende Münze zu wählen, scheitert bei Coin Change. Beispiel: coins=[1, 3, 4], amount=6. Greedy wählt zuerst 4 und anschließend 1+1 — insgesamt 3 Münzen. Optimal sind 3+3, also 2 Münzen. Bei den Standardstückelungen (1, 5, 10, 25 Cent) funktioniert Greedy, weil diese zufällig die Greedy-Eigenschaft erfüllen. Für beliebige Münzmengen ist jedoch DP erforderlich. Das ist ein klassischer Punkt in Bewerbungsgesprächen: Wenn Sie feststellen, dass Greedy scheitert, und erklären, warum, zeigen Sie ausgeprägtes analytisches Denkvermögen.
# Greedy failure example:
# coins=[1,3,4], amount=6
# Greedy: 4 (rem=2), 1 (rem=1), 1 (rem=0) -> 3 coins
# Optimal: 3 (rem=3), 3 (rem=0) -> 2 coins
def coin_change_greedy_wrong(coins, amount):
coins_sorted = sorted(coins, reverse=True)
count = 0
for coin in coins_sorted:
while amount >= coin:
amount -= coin
count += 1
return count if amount == 0 else -1
print('Greedy:', coin_change_greedy_wrong([1,3,4], 6)) # 3 (WRONG)
print('DP: ', coin_change([1,3,4], 6)) # 2 (CORRECT)Coin Change II: Die Möglichkeiten zählen
Coin Change II (LeetCode #518) fragt nach der Anzahl der Möglichkeiten, den Betrag zu bilden, nicht nach der minimalen Anzahl. Die Rekurrenz ändert sich: Statt min wird eine Summe verwendet. Für jede Münze gilt dp[i] += dp[i-coin]. Die Füllreihenfolge ist entscheidend: Um jede Kombination genau einmal zu zählen, durchlaufen Sie die Münzen in der äußeren Schleife und die Beträge in der inneren Schleife. Werden die Schleifen vertauscht, zählen Sie Permutationen statt Kombinationen — also ein anderes Problem.
def coin_change_ii(coins, amount):
# dp[i] = number of ways to make amount i
dp = [0] * (amount + 1)
dp[0] = 1 # one way to make amount 0: use no coins
# Outer loop: coins -- ensures each coin type processed once
for coin in coins:
# Inner loop: amounts
for i in range(coin, amount + 1):
dp[i] += dp[i - coin]
return dp[amount]
print(coin_change_ii([1, 2, 5], 5)) # 4: [1,1,1,1,1],[1,1,1,2],[1,2,2],[5]
print(coin_change_ii([2], 3)) # 0: impossible
print(coin_change_ii([10], 10)) # 1
# Key: coin outer, amount inner = COMBINATIONS (unordered)
# Reverse (amount outer, coin inner) = PERMUTATIONS (ordered)Min-Cost Staircase: Das Problem
Min Cost Climbing Stairs (LeetCode #746) gibt eine Treppe vor, bei der jede Stufe Kosten verursacht. Sie können jeweils 1 oder 2 Stufen auf einmal erklimmen. Finden Sie die minimalen Kosten, um die Spitze zu erreichen, also eine Stufe hinter der letzten Treppenstufe. Sie können kostenlos bei Stufe 0 oder Stufe 1 beginnen. Dieses Problem verbindet auf elegante Weise die Rekurrenz von Climbing Stairs mit dem Muster der Kostenminimierung aus Coin Change und bildet dadurch eine natürliche Brücke zwischen beiden Problemen.
# cost = [10, 15, 20]
# Pay cost[i] to leave step i
# You can step to i+1 or i+2
# Goal: reach top (index 3) with minimum cost
# Path options:
# Start at 0: cost 10, go to 2: cost 20, done -> 30
# Start at 1: cost 15, go to 3: done -> 15 <- OPTIMAL
# Start at 0: cost 10, go to 1: cost 15 -> 25
cost = [10, 15, 20]
# Optimal: start at step 1, pay 15, jump to top -> cost = 15
print('Expected:', 15)Min-Cost Staircase: Die Rekurrenz
Definieren Sie dp[i] als die minimalen Kosten, um Stufe i zu erreichen. Sie gelangen zu Stufe i, indem Sie cost[i-1] von Stufe i-1 oder cost[i-2] von Stufe i-2 bezahlen. Daher gilt dp[i] = min(dp[i-1] + cost[i-1], dp[i-2] + cost[i-2]). Basisfälle: dp[0] = 0, da der Start vor der Treppe kostenlos ist, und dp[1] = 0, da Sie auch kostenlos bei Stufe 1 beginnen können. Die Antwort ist dp[n], wobei n = len(cost) gilt.
def min_cost_climbing_stairs(cost):
n = len(cost)
# dp[i] = minimum cost to reach step i
# Steps 0 to n; step n is the top (goal)
dp = [0] * (n + 1)
# dp[0] = 0 (free to start here)
# dp[1] = 0 (free to start here)
for i in range(2, n + 1):
dp[i] = min(dp[i-1] + cost[i-1], # step from i-1
dp[i-2] + cost[i-2]) # jump from i-2
return dp[n]
print(min_cost_climbing_stairs([10, 15, 20])) # 15
print(min_cost_climbing_stairs([1,100,1,1,1,100,1,1,100,1])) # 6Min-Cost Staircase: Speicheroptimierung
Da dp[i] nur von dp[i-1] und dp[i-2] abhängt, können wir den Speicher wie bei Fibonacci mit zwei Variablen auf O(1) reduzieren. Ersetzen Sie das Array durch prev2 und prev1. Aktualisieren Sie die beiden Variablen bei jedem Schritt. Das ist eine typische Optimierung in einem einzigen Satz, die Interviewer erwarten, nachdem Sie die Tabellenlösung mit O(n) Speicher vorgestellt haben. Erwähnen Sie sie immer proaktiv: „Wir können den Speicher auf O(1) reduzieren, da wir nur die letzten beiden Werte benötigen.“
def min_cost_optimised(cost):
n = len(cost)
prev2, prev1 = 0, 0 # dp[0] and dp[1]
for i in range(2, n + 1):
curr = min(prev1 + cost[i-1], prev2 + cost[i-2])
prev2, prev1 = prev1, curr
return prev1
print(min_cost_optimised([10, 15, 20])) # 15
print(min_cost_optimised([1,100,1,1,1,100,1,1,100,1])) # 6
# Alternative: directly use cost array as rolling storage
def min_cost_v2(cost):
n = len(cost)
for i in range(2, n):
cost[i] += min(cost[i-1], cost[i-2])
return min(cost[-1], cost[-2])
from copy import deepcopy
cost_test = [10,15,20]
print(min_cost_v2(deepcopy(cost_test))) # 15Alternative DP-Formulierung
Für manche Probleme gibt es mehrere gültige DP-Formulierungen. Beim Min-Cost Staircase können Sie dp[i] als die minimalen Kosten definieren, um Stufe i zu VERLASSEN, wobei Sie cost[i] bezahlen und wählen, ob Sie zu i+1 oder i+2 gehen. Dann gilt dp[i] = cost[i] + min(dp[i+1], dp[i+2]); die Füllung erfolgt von rechts nach links, und die Antwort ist min(dp[0], dp[1]). Beide Formulierungen sind korrekt. Üben Sie, klar zu erläutern, welche Formulierung Sie gewählt haben und warum — das zeigt Ihre Sicherheit im Umgang mit DP.
def min_cost_alternative(cost):
n = len(cost)
# dp[i] = min cost when starting FROM step i
# Fill right to left
dp = cost[:] + [0] # dp[n] = 0 (already at top)
for i in range(n - 1, -1, -1):
# Pay cost[i], then choose i+1 or i+2
if i + 2 <= n:
dp[i] = cost[i] + min(dp[i+1], dp[i+2])
else:
dp[i] = cost[i] + dp[i+1]
# Can start at step 0 or step 1
return min(dp[0], dp[1])
print(min_cost_alternative([10, 15, 20])) # 15
print(min_cost_alternative([1,100,1,1,1,100,1,1,100,1])) # 6Coin Change und Staircase verbinden
Coin Change und Min-Cost Staircase sind beide Beispiele für dasselbe DP-Muster: Bei jedem Schritt wird eine Auswahl aus einer endlichen Menge von Möglichkeiten getroffen und über die Folge dieser Entscheidungen ein Zielwert optimiert. Die Unterschiede sind eher oberflächlich: Bei Coin Change wird die Anzahl erfasst, indem pro Münze 1 addiert wird, während beim Staircase-Problem die Kosten erfasst werden, indem pro Schritt cost[i] addiert wird. Wenn Sie diese gemeinsame Struktur erkennen, können Sie neue DP-Probleme lösen, indem Sie sie auf vertraute Muster abbilden.
# Shared pattern:
# dp[state] = optimise(dp[prev_state_1] + cost_1,
# dp[prev_state_2] + cost_2, ...)
# Coin change: dp[amount] = min(1 + dp[amount - coin] for coin in coins)
# Min stair: dp[step] = min(cost[step-1]+dp[step-1], cost[step-2]+dp[step-2])
# Max path sum: dp[cell] = max(dp[top], dp[left]) + grid[cell]
# House robber: dp[house] = max(dp[house-1], dp[house-2] + value[house])
# All four are the SAME pattern with different:
# - State representation
# - Number of choices per state
# - Objective (min/max)
# - Transition cost
print('DP pattern: state + choices + objective + cost = template')Minimale Anzahl perfekter Quadrate
Perfect Squares (LeetCode #279) fragt nach der minimalen Anzahl perfekter Quadrate (1, 4, 9, 16, ...), deren Summe n ergibt. Das entspricht genau Coin Change, wobei die „Münzen“ perfekte Quadratzahlen sind. Erzeugen Sie alle perfekten Quadrate bis n und führen Sie anschließend Coin Change aus. Mit DP ergibt sich eine Laufzeit von O(n * sqrt(n)). Der Vierquadratesatz von Lagrange besagt, dass die Antwort höchstens 4 ist, was auch einen mathematischen Ansatz mit O(sqrt(n)) ermöglicht — erwartet wird jedoch die DP-Lösung.
import math
def num_squares(n):
# Generate all perfect squares up to n
squares = [i*i for i in range(1, int(math.sqrt(n)) + 1)]
# Coin change with squares as 'coins'
dp = [float('inf')] * (n + 1)
dp[0] = 0
for i in range(1, n + 1):
for sq in squares:
if i >= sq:
dp[i] = min(dp[i], 1 + dp[i - sq])
return dp[n]
print(num_squares(12)) # 3: 4+4+4
print(num_squares(13)) # 2: 4+9
print(num_squares(1)) # 1: 1DP debuggen: Häufige Fehler
Häufige DP-Fehler sind: ein falscher Basisfall (dp[0] wurde falsch gesetzt), eine falsche Füllreihenfolge (ein Wert wird abgerufen, bevor er berechnet wurde), ein Off-by-one-Fehler bei der Zustandsdefinition (dp[i] sind die Kosten, um i zu erreichen, oder die Kosten, i zu verlassen) und das fehlende Zurückgeben von -1, wenn Unendlich verbleibt (unmögliche Fälle). Testen Sie immer zuerst die einfachsten Fälle — leere Eingabe, ein einzelnes Element und target=0 — bevor Sie größere Eingaben prüfen.
# Common DP debugging checklist:
# 1. Base case: what is dp[0]? dp[1]? Are they correct?
# 2. State definition: write it in English before coding
# 3. Recurrence: trace manually on a 3-element example
# 4. Fill order: dependency arrows point left/up? Fill left/up first
# 5. Infinity check: return -1 or 0 when dp[target] == inf?
# 6. Array bounds: dp has size n+1 for 0..n, or n for 0..n-1?
# Quick test template:
def test_coin_change():
assert coin_change([1], 0) == 0 # base case
assert coin_change([1], 1) == 1 # single coin
assert coin_change([2], 3) == -1 # impossible
assert coin_change([1,5,6,9], 11) == 2
print('All tests passed!')
test_coin_change()Schnelltest
Testen Sie Ihr Verständnis der Konzepte aus Data Structures & Algorithms — Coding Interview Prep in dieser Lektion.
Zusammenfassung der Lektion
In dieser Lektion haben Sie DP zur Minimierung der Münzanzahl bei Coin Change (unbeschränktes Rucksackproblem) und die Gründe für das Scheitern von Greedy gelernt, außerdem Coin Change II zum Zählen von Kombinationen mit der Reihenfolge Münzen außen und Beträge innen sowie Min-Cost Staircase mit Formulierungen von links nach rechts und von rechts nach links. Als Nächstes untersuchen wir 1D-DP-Muster mit House Robber, Kadanes Algorithmus und Word Break.
Häufig gestellte Fragen
Ist die Lektion „Coin Change und Treppe mit minimalen Kosten“ kostenlos?
Ja — der vollständige Text von „Coin Change und Treppe mit minimalen Kosten“ ist hier im Web kostenlos zu lesen. Um sie interaktiv zu üben (integrierter Code-Editor und 24/7 KI-Tutor) und den Rest des Coding Interview Prep-Kurses freizuschalten, upgrade auf CoddyKit PRO. Der Coding Interview Prep-Kurs umfasst insgesamt 4 Lektionen.
Was lerne ich in „Coin Change und Treppe mit minimalen Kosten“?
Formulieren Sie die Rekurrenzen für coin-change und min-cost-climbing-stairs, wählen Sie die richtige DP-Richtung und verfolgen Sie die Tabelle manuell. Du übst Coding Interview Prep mit praktischem Code, den du direkt im Browser ausführst, und ein 24/7 KI-Tutor beantwortet deine Fragen während du die Lektion bearbeitest.
Brauche ich Erfahrung, um Coding Interview Prep zu starten?
Keine Vorkenntnisse erforderlich. Coding Interview Prep auf CoddyKit ist für Anfänger bis fortgeschrittene Lernende strukturiert, sodass du hier starten oder von Anfang an beginnen und in deinem eigenen Tempo voranschreiten kannst. Dies ist Lektion 4 von 4.
Wie lange dauert die Lektion „Coin Change und Treppe mit minimalen Kosten“?
Die meisten CoddyKit-Lektionen dauern etwa 5–10 Minuten. Jede ist kompakt und interaktiv, sodass du stetig Fortschritte machst und genau dort weitermachst, wo du aufgehört hast – im Web und in der App.
Kann ich in dieser Coding Interview Prep-Lektion Code schreiben und ausführen?
Ja. Jede Coding Interview Prep-Lektion enthält einen integrierten Code-Editor, sodass du echten Code direkt in deinem Browser schreibst und ausführst und sofort KI-Feedback erhältst — ohne lokale Einrichtung erforderlich.
Alle Lektionen in diesem Kurs
- DP erkennen: Überlappende Teilprobleme
- Top-Down-DP mit Memoisation
- Bottom-Up-DP mit Tabellierung
- Coin Change und Treppe mit minimalen Kosten