Greedy versus DP: wanneer gebruikt u welke methode
Herken de kenmerken van problemen die met een greedy-aanpak kunnen worden opgelost versus problemen waarvoor DP nodig is, aan de hand van de greedy-choice-eigenschap en het exchange-argument.
Greedy versus DP: wanneer gebruikt u welke methode is een gratis DSA Interview Prep-les op CoddyKit. Dit is les 1 van 4. Je kunt 3 lessen uit dit leerpad gratis volledig lezen — daarna ontgrendelt CoddyKit PRO alle lessen, plus praktische oefeningen met een ingebouwde code-editor en een AI-tutor die 24/7 beschikbaar is. Deze les maakt deel uit van het leertraject DSA Interview Prep. Je voortgang wordt gesynchroniseerd op het web en in de CoddyKit-app. De cursus DSA Interview Prep bevat in totaal 4 lessen.
Overzicht van gulzige algoritmen en DP
Zowel gulzige algoritmen als dynamische programmering lossen optimalisatieproblemen op — ze zoeken een maximum, minimum of optimale indeling. Een gulzige aanpak maakt bij elke stap de lokaal optimale keuze zonder eerdere beslissingen opnieuw te overwegen. DP onderzoekt alle mogelijkheden, maar gebruikt memoization om herberekeningen te voorkomen. Weten welke aanpak je moet gebruiken, kan uren debuggen van een onjuiste gulzige aanpak of een onnodig ingewikkelde DP-tabel besparen.
# 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')Eigenschap van de gulzige keuze
Een probleem heeft de eigenschap van de gulzige keuze wanneer je altijd een globaal optimale oplossing kunt opbouwen door lokaal optimale (gulzige) keuzes te maken. Formeel: er bestaat een optimale oplossing die met de gulzige keuze begint, zodat je nooit hoeft terug te gaan. Om dit te bewijzen gebruik je meestal een omwisselingsargument: veronderstel dat een willekeurige optimale oplossing de gulzige keuze niet bevat en laat vervolgens zien dat je die keuze kunt omwisselen zonder het resultaat te verslechteren.
# 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 deelstructuur
Zowel gulzige algoritmen als DP vereisen een optimale deelstructuur: de optimale oplossing voor het volledige probleem bevat optimale oplossingen voor deelproblemen. Het verschil is of je optimale oplossingen voor deelproblemen gulzig kunt bepalen (zonder alle opties te onderzoeken) of dat je meerdere keuzes moet vergelijken. Als je een keuze maakt en het resterende deelprobleem dezelfde structuur heeft, werkt een gulzige aanpak. Als je meerdere keuzes moet vergelijken, gebruik je 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')Overlappende deelproblemen: signaal voor DP
Als hetzelfde deelprobleem meerdere keren wordt opgelost in een recursieve ontleding, heb je DP met memoization nodig. Teken de recursieboom en zoek naar herhaalde knopen. Voor Fibonacci wordt fib(3) in de boom voor fib(5) twee keer berekend. Bij wisselgeld met munten [1,3,4] en doel 6 komen de deelproblemen voor doelwaarden 3, 2 en 1 meerdere keren voor. Overlappende deelproblemen plus een optimale deelstructuur = 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)Klassieke gulzige problemen
Problemen waarvoor een gulzige aanpak aantoonbaar correct is: (1) activiteiten- en intervalplanning — kies gulzig op basis van de vroegste eindtijd. (2) minimale opspannende boom — de algoritmen van Prim en Kruskal. (3) Huffman-codering — voeg altijd de twee knopen met de laagste frequentie samen. (4) fractioneel rugzakprobleem — neem items op basis van de hoogste waarde-gewichtsverhouding. (5) sprongspel — houd de maximaal bereikbare index bij. Voor al deze problemen bestaat een rechtvaardiging met een bewijs via een omwisselingsargument.
# 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)Wanneer gulzig niet werkt: tegenvoorbeelden
Een tegenvoorbeeld vinden is de snelste manier om een hypothese over een gulzige aanpak te weerleggen. Bij wisselgeld met munten [1, 3, 4] en doel 6 kiest de gulzige aanpak (grootste munt eerst) 4 en daarna 1+1: 3 munten. DP vindt 3+3: 2 munten. Bij het 0/1-rugzakprobleem kiest de gulzige aanpak op basis van de verhouding het item met de beste verhouding, maar kunnen combinaties die de capaciteit beter vullen buiten beeld blijven. Als je binnen een minuut een tegenvoorbeeld kunt construeren, stap dan over op 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)Vergelijkingstabel: gulzig versus DP
De belangrijkste verschillen naast elkaar: tijdscomplexiteit — een gulzige aanpak heeft meestal O(n log n) (voornamelijk door het sorteren); DP heeft O(n × toestanden). ruimtecomplexiteit — een gulzige aanpak gebruikt O(1) extra ruimte; DP gebruikt O(toestanden). correctheid — voor een gulzige aanpak heb je een bewijs nodig; DP is altijd correct als de toestanden en de recursierelatie juist zijn. toepasbaarheid — een gulzige aanpak voor planning, opspannende bomen en Huffman; DP voor rugzakproblemen, reeksuitlijning en kortste paden met negatieve 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')Besliskader
Beslisstroom voor sollicitatiegesprekken: (1) Kun je de eigenschap van de gulzige keuze bewijzen met een omwisselingsargument? Zo ja → gulzige aanpak. (2) Overlappen deelproblemen (wordt dezelfde toestand op meerdere manieren bereikt)? Zo ja → DP. (3) Vraagt het probleem om het aantal oplossingen te tellen of alle oplossingen te enumereren? → DP of backtracking. (4) Vraagt het probleem om één optimale waarde met een natuurlijke volgorde? Denk dan aan een gulzige aanpak. (5) Bij twijfel: codeer de DP — die is altijd correct als de recursierelatie juist is, ook als de aanpak trager is.
# 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)')Intervalproblemen: gulzig versus DP
Intervalproblemen vallen uiteen in problemen voor een gulzige aanpak en problemen voor DP. Bij niet-overlappende intervallen (verwijder er zo weinig mogelijk) sorteer je op eindtijd en kies je gulzig intervallen — dit is aantoonbaar optimaal. Bij gewogen intervalplanning (maximaliseer het totale gewicht) heb je DP nodig, omdat zware intervallen veel lichte intervallen kunnen overlappen en je alle geldige deelverzamelingen moet vergelijken. Het onderscheidende kenmerk is of alle intervallen hetzelfde gewicht hebben (gulzige aanpak) of een variabel gewicht hebben (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]])) # 2Probleemsignalen herkennen
Veelvoorkomende signalen in probleemomschrijvingen: 'minimumaantal bewerkingen', 'maximale winst', 'optimale selectie' → dit kan gulzig of DP zijn; controleer op overlap. 'tel het aantal manieren' → altijd DP. 'vind een geldig schema' → dit kan gulzig zijn. 'alle mogelijke' → backtracking. 'kan geen aangrenzende elementen nemen' → DP (het huisroofprobleem). 'vergaderingen, intervallen, taken' → waarschijnlijk gulzig. Door signalen aan algoritmefamilies te koppelen, stel je in sollicitatiegesprekken sneller vast om welk probleem het gaat.
# 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}')Correctheid van gulzige algoritmen bewijzen
Gebruik het omwisselingsargument om de correctheid van een gulzig algoritme te bewijzen: (1) Neem aan dat er een optimale oplossing OPT bestaat die op de eerste keuze afwijkt van de gulzige oplossing G. (2) Laat zien dat je de gulzige keuze in OPT kunt opnemen zonder de doelfunctiewaarde te verhogen. (3) Met inductie volgt dat de gulzige oplossing minstens zo goed is als elke optimale oplossing. In sollicitatiegesprekken heb je geen volledig bewijs nodig, maar als je de intuïtie achter het omwisselingsargument uitlegt, toon je diep inzicht.
# 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')Korte controle
Test je begrip van de concepten uit Data Structures & Algorithms — Coding Interview Prep in deze les.
Samenvatting van de les
In deze les heb je geleerd: een gulzige aanpak is correct wanneer de eigenschap van de gulzige keuze geldt — dit kun je bewijzen via een omwisselingsargument, je hebt DP nodig wanneer deelproblemen overlappen (hetzelfde deelprobleem via meerdere routes wordt bereikt) en niet met één gulzige regel kunnen worden opgelost, en de snelste manier om een hypothese over een gulzige aanpak te weerleggen, is een tegenvoorbeeld met niet-standaardinvoer te construeren. Hierna lossen we intervalplanning en het samenvoegen van intervallen op met de gulzige aanpak die op eindtijd sorteert.
Leer Python met een AI-tutor — gratis
Schrijf echte code en voer die uit in je browser, krijg direct hulp van een AI-tutor die 24/7 beschikbaar is en ga verder waar je gebleven bent op het web of in de app.
- Cursussen
- 30
- Lessen
- 120
Veelgestelde vragen
Is de les “Greedy versus DP: wanneer gebruikt u welke methode” gratis?
Ja — je kunt hier op het web alle 3 lessen van het leerpad DSA Interview Prep, waaronder “Greedy versus DP: wanneer gebruikt u welke methode”, gratis volledig lezen. Daarna ontgrendelt CoddyKit PRO alle lessen, plus interactieve oefeningen met een ingebouwde code-editor en een AI-tutor die 24/7 beschikbaar is. De cursus DSA Interview Prep bevat in totaal 4 lessen.
Wat leer ik in “Greedy versus DP: wanneer gebruikt u welke methode”?
Herken de kenmerken van problemen die met een greedy-aanpak kunnen worden opgelost versus problemen waarvoor DP nodig is, aan de hand van de greedy-choice-eigenschap en het exchange-argument. Je oefent met DSA Interview Prep door code rechtstreeks in de browser uit te voeren. Een AI-begeleider die 24/7 beschikbaar is beantwoordt je vragen terwijl je de les doorwerkt.
Heb ik ervaring nodig om met DSA Interview Prep te beginnen?
Ervaring vooraf is niet nodig. DSA Interview Prep op CoddyKit is opgebouwd voor beginners tot gevorderden, zodat je hier of bij het begin kunt starten en in je eigen tempo kunt leren. Dit is les 1 van 4.
Hoe lang duurt de les “Greedy versus DP: wanneer gebruikt u welke methode”?
De meeste lessen van CoddyKit duren ongeveer 5–10 minuten. Elke les is kort en interactief, zodat je gestaag vooruitgaat en op het web en in de app precies verdergaat waar je was gebleven.
Kan ik code schrijven en uitvoeren in deze les over DSA Interview Prep?
Ja. Elke les over DSA Interview Prep bevat een ingebouwde code-editor, zodat je rechtstreeks in je browser echte code kunt schrijven en uitvoeren en direct feedback van AI krijgt — lokale installatie is niet nodig.
Alle lessen in deze cursus
- Greedy versus DP: wanneer gebruikt u welke methode
- Intervalplanning en samenvoegen
- Jump Game I en II
- Task Scheduler en Gas Station