Greedy vs. dynamische Programmierung: Wann wird was verwendet?
Erkennen Sie anhand der Greedy-Choice-Eigenschaft und des Austauscharguments, welche Probleme sich per Greedy lösen lassen und welche dynamische Programmierung erfordern.
Greedy vs. dynamische Programmierung: Wann wird was verwendet? ist eine kostenlose DSA Interview Prep-Lektion auf CoddyKit. Dies ist Lektion 1 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 DSA Interview Prep-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der DSA Interview Prep-Kurs umfasst insgesamt 4 Lektionen.
Überblick über Greedy und DP
Sowohl Greedy als auch Dynamic Programming lösen Optimierungsprobleme – sie suchen ein Maximum, ein Minimum oder eine optimale Anordnung. Greedy trifft in jedem Schritt die lokal optimale Entscheidung, ohne frühere Entscheidungen noch einmal zu überdenken. DP untersucht alle Möglichkeiten, verwendet aber Memoisation, um erneute Berechnungen zu vermeiden. Wenn Sie wissen, welcher Ansatz geeignet ist, können Sie sich stundenlanges Debugging eines falschen Greedy-Ansatzes oder einer unnötig komplexen DP-Tabelle ersparen.
# Greedy: always take the locally best option
# Example: coin change with coins [1, 5, 10, 25]
# Greedy: take as many 25s as possible, then 10s, etc.
# This works for standard denominations but NOT all coin sets!
# DP: explore all possibilities via memoisation
# Example: coin change with coins [1, 3, 4] and target 6
# Greedy would pick 4, then 1, 1 → 3 coins
# DP finds: 3 + 3 → 2 coins (optimal!)
print('Greedy can fail when local optimum != global optimum')Das Greedy-Wahlprinzip
Ein Problem erfüllt das Greedy-Wahlprinzip, wenn eine global optimale Lösung immer durch lokal optimale (Greedy-)Entscheidungen konstruiert werden kann. Formal bedeutet das: Es gibt eine optimale Lösung, die mit der Greedy-Entscheidung beginnt, sodass kein Backtracking erforderlich ist. Zum Beweis wird typischerweise ein Austauschargument verwendet: Nehmen Sie an, eine beliebige optimale Lösung enthält die Greedy-Entscheidung nicht, und zeigen Sie anschließend, dass Sie diese Entscheidung einsetzen können, ohne das Ergebnis zu verschlechtern.
# Exchange argument example: Activity Selection
# Greedy: always pick the activity that ends earliest
# Proof: suppose optimal solution starts with activity A (not earliest-ending)
# Let G be the earliest-ending activity.
# Replace A with G in the solution:
# - G ends no later than A, so G does not conflict with any activity A allowed
# - The solution remains valid with at least as many activities
# Therefore greedy choice (earliest end) is always safe.
activities = [(1,4), (3,5), (0,6), (5,7), (3,9), (5,9), (6,10), (8,11), (8,12), (2,14)]
activities.sort(key=lambda x: x[1]) # sort by end time
print('Sorted by end:', activities[:4], '...')Optimale Teilstruktur
Sowohl Greedy als auch DP benötigen eine optimale Teilstruktur: Die optimale Lösung des Gesamtproblems enthält optimale Lösungen für Teilprobleme. Der Unterschied besteht darin, ob sich die optimalen Lösungen der Teilprobleme Greedy bestimmen lassen (ohne alle Möglichkeiten zu untersuchen) oder ob mehrere Entscheidungen verglichen werden müssen. Wenn Sie eine Entscheidung treffen und das verbleibende Teilproblem dieselbe Struktur hat, funktioniert Greedy. Wenn Sie mehrere Entscheidungen vergleichen müssen, verwenden Sie DP.
# Greedy works: activity selection
# Making the greedy choice (earliest-ending) leaves a sub-problem
# that is structurally identical (activity selection on remaining activities)
# and the greedy choice for the sub-problem is still valid.
# DP needed: 0/1 knapsack
# After choosing to include/exclude item i, the remaining sub-problem
# depends on WHICH item we chose — different choices yield different sub-problems.
# No single greedy rule works for all inputs.
print('Greedy: sub-problem is unique after each choice')
print('DP: sub-problem depends on which choice was made')Signal für überlappende Teilprobleme und DP
Wenn dasselbe Teilproblem in einer rekursiven Zerlegung mehrfach gelöst wird, benötigen Sie DP mit Memoisation. Zeichnen Sie den Rekursionsbaum und suchen Sie nach wiederholten Knoten. Bei Fibonacci wird fib(3) im Baum für fib(5) zweimal berechnet. Beim Münzwechsel mit den Münzen [1,3,4] und dem Zielwert 6 treten die Teilprobleme für die Zielwerte 3, 2 und 1 mehrfach auf. Überlappende Teilprobleme plus optimale Teilstruktur = DP.
# Recursion tree for coin change [1,3,4], target=6
# bt(6) → bt(5) → bt(4) → bt(3) (repeated!)
# → bt(2) → bt(1) (repeated!)
# → bt(3) (repeated!)
# → bt(2) (repeated!)
# Without memoisation: exponential time
# With DP table: O(target * len(coins)) time
def coin_change_dp(coins, amount):
dp = [float('inf')] * (amount + 1)
dp[0] = 0
for a in range(1, amount + 1):
for c in coins:
if c <= a:
dp[a] = min(dp[a], dp[a - c] + 1)
return dp[amount] if dp[amount] != float('inf') else -1
print(coin_change_dp([1, 3, 4], 6)) # 2 (3+3)
print(coin_change_dp([2], 3)) # -1 (impossible)Klassische Greedy-Probleme
Probleme, bei denen Greedy nachweislich korrekt ist: (1) Aktivitäts-/Intervallplanung – Greedy nach frühestem Endzeitpunkt. (2) Minimaler Spannbaum – die Algorithmen von Prim und Kruskal. (3) Huffman-Kodierung – immer die beiden Knoten mit der niedrigsten Häufigkeit zusammenführen. (4) Bruchteiliger Rucksack – Elemente nach dem höchsten Wert-Gewicht-Verhältnis auswählen. (5) Jump Game – den maximal erreichbaren Index verfolgen. Für all diese Probleme gibt es eine Begründung durch ein Austauschargument.
# Fractional Knapsack: greedy works
def fractional_knapsack(items, capacity):
# Sort by value/weight ratio descending
items.sort(key=lambda x: x[1]/x[0], reverse=True)
total = 0
for weight, value in items:
if capacity <= 0: break
take = min(weight, capacity)
total += take * (value / weight)
capacity -= take
return total
items = [(10, 60), (20, 100), (30, 120)] # (weight, value)
print(fractional_knapsack(items, 50)) # 240.0
# 0/1 Knapsack: greedy FAILS
# Must use DP (can't take fractions)Wenn Greedy scheitert: Gegenbeispiele
Ein Gegenbeispiel zu finden, ist der schnellste Weg, eine Greedy-Hypothese zu widerlegen. Beim Münzwechsel mit den Münzen [1, 3, 4] und dem Zielwert 6 wählt Greedy (größte Münze zuerst) 4 und anschließend 1+1 – insgesamt 3 Münzen. DP findet 3+3 – insgesamt 2 Münzen. Beim 0/1-Rucksack wählt Greedy nach dem Verhältnis zwar das Element mit dem besten Verhältnis, kann aber Kombinationen übersehen, die die Kapazität besser ausfüllen. Wenn Sie innerhalb einer Minute ein Gegenbeispiel konstruieren können, wechseln Sie zu DP.
# Counterexample: coin change with non-standard coins
def greedy_coins(coins, amount):
coins.sort(reverse=True)
count = 0
for c in coins:
while amount >= c:
amount -= c
count += 1
return count if amount == 0 else -1
def dp_coins(coins, amount):
dp = [float('inf')] * (amount + 1)
dp[0] = 0
for a in range(1, amount + 1):
for c in coins:
if c <= a: dp[a] = min(dp[a], dp[a-c] + 1)
return dp[amount] if dp[amount] < float('inf') else -1
coins, target = [1, 3, 4], 6
print('Greedy:', greedy_coins(coins[:], target)) # 3 (4+1+1)
print('DP: ', dp_coins(coins, target)) # 2 (3+3)Vergleichstabelle: Greedy vs. DP
Die wichtigsten Unterschiede im direkten Vergleich: Zeitkomplexität – Greedy typischerweise O(n log n) (bestimmt durch das Sortieren); DP O(n × Zustände). Speicherkomplexität – Greedy O(1) zusätzlicher Speicher; DP O(Zustände). Korrektheit – für Greedy ist ein Beweis erforderlich; DP ist immer korrekt, wenn Zustände und Rekurrenz korrekt sind. Anwendbarkeit – Greedy für Planung, Spannbäume und Huffman; DP für Rucksackprobleme, Sequenz-Alignment und kürzeste Wege mit negativen Gewichten.
# Performance comparison
import time
def time_it(func, *args):
start = time.time()
result = func(*args)
return result, time.time() - start
# Large coin change test
coins = [1, 5, 10, 25, 100]
amount = 10000
def dp_coins(coins, amount):
dp = [float('inf')] * (amount + 1)
dp[0] = 0
for a in range(1, amount + 1):
for c in coins:
if c <= a: dp[a] = min(dp[a], dp[a-c]+1)
return dp[amount]
result, elapsed = time_it(dp_coins, coins, amount)
print(f'DP coin change(amount={amount}): {result} coins in {elapsed:.4f}s')Entscheidungsrahmen
Entscheidungsablauf im Interview: (1) Können Sie das Greedy-Wahlprinzip mit einem Austauschargument beweisen? Wenn ja → Greedy. (2) Überlappen sich Teilprobleme (wird derselbe Zustand auf mehreren Wegen erreicht)? Wenn ja → DP. (3) Fragt das Problem nach dem Zählen oder dem Auflisten aller Lösungen? → DP oder Backtracking. (4) Fragt das Problem nach einem einzelnen optimalen Wert mit einer natürlichen Ordnung? Dann sollten Sie Greedy in Betracht ziehen. (5) Wenn Sie unsicher sind, implementieren Sie die DP-Lösung – sie ist bei korrekter Rekurrenz immer richtig, auch wenn sie langsamer ist.
# Decision questions to ask:
questions = [
'1. Is there a natural ordering (by time, ratio, size)?',
'2. Does making the greedy choice leave a smaller same-type problem?',
'3. Can I construct a counterexample quickly?',
'4. Are sub-problems reused across different choice sequences?',
'5. Does the problem involve counting or listing (not just optimising)?',
]
for q in questions:
print(q)
print()
print('Greedy signals: scheduling, spanning tree, Huffman, jump game')
print('DP signals: knapsack, edit distance, LCS, coin change (general)')Intervallprobleme: Greedy vs. DP
Intervallprobleme teilen sich in Greedy- und DP-Probleme auf. Überlappungsfreie Intervalle (möglichst wenige entfernen): Sortieren Sie nach dem Endzeitpunkt und wählen Sie Intervalle Greedy aus – Greedy ist nachweislich optimal. Gewichtete Intervallplanung (Gesamtgewicht maximieren): Hier ist DP erforderlich, weil schwere Intervalle viele leichte Intervalle überlappen können und alle gültigen Teilmengen verglichen werden müssen. Der entscheidende Faktor ist, ob alle Intervalle das gleiche Gewicht haben (Greedy) oder unterschiedliche Gewichte (DP).
# Non-overlapping intervals: greedy works
def erase_overlap_intervals(intervals):
if not intervals: return 0
intervals.sort(key=lambda x: x[1])
count = 0
last_end = float('-inf')
for start, end in intervals:
if start >= last_end:
last_end = end # keep this interval
else:
count += 1 # remove this interval
return count
print(erase_overlap_intervals([[1,2],[2,3],[3,4],[1,3]])) # 1
print(erase_overlap_intervals([[1,2],[1,2],[1,2]])) # 2Probleme anhand von Signalen erkennen
Häufige Signale in Aufgabenstellungen: „minimale Anzahl von Operationen“, „maximaler Gewinn“, „optimale Auswahl“ → könnte Greedy oder DP sein, prüfen Sie die Überlappung. „Zählen Sie die Anzahl der Möglichkeiten“ → immer DP. „Finden Sie einen beliebigen gültigen Ablaufplan“ → könnte Greedy sein. „alle möglichen“ → Backtracking. „darf keine benachbarten Elemente nehmen“ → DP (House Robber). „Besprechungen, Intervalle, Aufgaben“ → wahrscheinlich Greedy. Wenn Sie Signale den Algorithmusfamilien zuordnen, können Sie Interviewaufgaben schneller diagnostizieren.
# Signal-to-algorithm mapping
signals = {
'minimum steps/coins/operations': 'DP (unless trivially greedy)',
'maximum profit/value with constraint': 'DP (knapsack family)',
'count ways to reach/achieve': 'DP (always)',
'all combinations/permutations': 'Backtracking',
'schedule tasks within time': 'Greedy (sort by deadline/end)',
'cannot pick adjacent': 'DP (house robber pattern)',
'free to pick any subset': 'DP or Greedy (check overlap)',
'interval merging/selecting': 'Greedy (sort by end time)',
}
for signal, algo in signals.items():
print(f'{signal!r}: → {algo}')Korrektheit von Greedy beweisen
Um die Korrektheit eines Greedy-Algorithmus zu beweisen, verwenden Sie das Austauschargument: (1) Nehmen Sie an, es gibt eine optimale Lösung OPT, die sich bei der ersten Entscheidung von der Greedy-Lösung G unterscheidet. (2) Zeigen Sie, dass Sie die Greedy-Entscheidung in OPT einsetzen können, ohne den Zielfunktionswert zu erhöhen. (3) Daraus folgt per Induktion, dass die Greedy-Lösung genauso gut ist wie jede optimale Lösung. Im Interview benötigen Sie keinen vollständigen Beweis, aber die Erläuterung der Idee hinter dem Austauschargument zeigt ein tiefes Verständnis.
# Exchange argument demo: earliest-finish-time activity selection
# Suppose OPT starts with activity A (not earliest-ending)
# Let G = earliest-ending activity available
# A.end >= G.end (G ends earlier or same time)
# Swap A for G in OPT:
# - G.end <= A.end, so G does not conflict with anything A allowed after it
# - OPT remains valid with the same number of activities
# - Repeat: after swap, OPT begins with G, matching greedy first choice
# By induction, OPT can be transformed to match G activity by activity
# without losing activities → greedy is optimal
print('Exchange argument: any OPT can be modified to match Greedy without loss')
print('This proves Greedy >= OPT in objective value')Kurzer Wissenstest
Testen Sie Ihr Verständnis der Konzepte aus Data Structures & Algorithms — Coding Interview Prep aus dieser Lektion.
Zusammenfassung der Lektion
In dieser Lektion haben Sie gelernt: Greedy ist korrekt, wenn das Greedy-Wahlprinzip gilt – dies lässt sich über ein Austauschargument beweisen, DP ist erforderlich, wenn sich Teilprobleme überlappen (dasselbe Teilproblem wird auf mehreren Wegen erreicht) und nicht durch eine einzelne Greedy-Regel gelöst werden können und der schnellste Weg, eine Greedy-Hypothese zu widerlegen, darin besteht, mit nicht standardmäßigen Eingaben ein Gegenbeispiel zu konstruieren. Als Nächstes lösen wir Intervallplanung und das Zusammenführen von Intervallen mit dem Greedy-Ansatz des Sortierens nach dem Endzeitpunkt.
Häufig gestellte Fragen
Ist die Lektion „Greedy vs. dynamische Programmierung: Wann wird was verwendet?“ kostenlos?
Ja — der vollständige Text von „Greedy vs. dynamische Programmierung: Wann wird was verwendet?“ ist hier im Web kostenlos zu lesen. Um sie interaktiv zu üben (integrierter Code-Editor und 24/7 KI-Tutor) und den Rest des DSA Interview Prep-Kurses freizuschalten, upgrade auf CoddyKit PRO. Der DSA Interview Prep-Kurs umfasst insgesamt 4 Lektionen.
Was lerne ich in „Greedy vs. dynamische Programmierung: Wann wird was verwendet?“?
Erkennen Sie anhand der Greedy-Choice-Eigenschaft und des Austauscharguments, welche Probleme sich per Greedy lösen lassen und welche dynamische Programmierung erfordern. Du übst DSA 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 DSA Interview Prep zu starten?
Keine Vorkenntnisse erforderlich. DSA 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 1 von 4.
Wie lange dauert die Lektion „Greedy vs. dynamische Programmierung: Wann wird was verwendet?“?
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 DSA Interview Prep-Lektion Code schreiben und ausführen?
Ja. Jede DSA 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
- Greedy vs. dynamische Programmierung: Wann wird was verwendet?
- Intervallplanung und Zusammenführen
- Jump Game I und II
- Task Scheduler und Gas Station