Memoisation: recursieve resultaten cachen
Pas @functools.lru_cache en handmatige memo-dicts toe op Fibonacci en climbing-stairs om exponentiële herberekening te elimineren.
Memoisation: recursieve resultaten cachen is een gratis DSA Interview Prep-les op CoddyKit. Dit is les 4 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.
Het probleem met overbodige recursie
Naïeve recursieve Fibonacci berekent dezelfde waarden steeds opnieuw. fib(5) roept fib(4) en fib(3) aan; fib(4) roept fib(3) en fib(2) aan — dus fib(3) wordt twee keer berekend. Deze overbodigheid groeit exponentieel: fib(40) voert meer dan een miljard functieaanroepen uit. Memoization lost dit op door elk resultaat op te slaan wanneer het voor het eerst wordt berekend, zodat volgende aanroepen het in O(1) kunnen ophalen in plaats van het opnieuw te berekenen.
# Count calls without memoisation
call_count = [0]
def fib_plain(n):
call_count[0] += 1
if n <= 1: return n
return fib_plain(n-1) + fib_plain(n-2)
fib_plain(20)
print(f'fib(20) without memo: {call_count[0]:,} calls')
# ~21,891 calls for n=20; ~1 billion for n=40Handmatige memoization met een woordenboek
Voeg een memo-woordenboek toe als parameter (of gebruik een closure). Controleer voordat je iets berekent of het antwoord al in memo staat. Zo ja, retourneer het onmiddellijk. Zo nee, bereken het, sla het op in memo en retourneer het. Elk uniek deelprobleem wordt nu precies één keer berekend, waardoor O(2^n) verandert in O(n) tijd en O(n) ruimte voor het memo-woordenboek, plus O(n) stackruimte.
def fib_memo(n, memo={}):
if n in memo:
return memo[n]
if n <= 1:
return n
memo[n] = fib_memo(n-1, memo) + fib_memo(n-2, memo)
return memo[n]
print(fib_memo(10)) # 55
print(fib_memo(50)) # 12586269025
print(fib_memo(100)) # huge number — still fast!De functools.lru_cache-decorator
Python biedt @functools.lru_cache(maxsize=None) (ook beschikbaar als @functools.cache in Python 3.9+) om memoization te automatiseren. Als je deze decorator boven een functie plaatst, worden alle aanroepen opgeslagen op basis van hun argumenten. maxsize=None betekent een onbeperkte cachegrootte — elke unieke combinatie van argumenten wordt opgeslagen. Hiermee verander je elke recursieve functie met één regel code in een memoized versie.
import functools
@functools.lru_cache(maxsize=None)
def fib(n):
if n <= 1:
return n
return fib(n-1) + fib(n-2)
print(fib(50)) # 12586269025
print(fib(100)) # 354224848179261915075
print(fib.cache_info()) # CacheInfo(hits=..., misses=..., maxsize=None, currsize=...)Trappen beklimmen (LeetCode 70)
LeetCode 70 'Trappen beklimmen': je kunt telkens 1 of 2 treden beklimmen. Op hoeveel manieren kun je trede n bereiken? Dit is in feite Fibonacci: ways(n) = ways(n-1) + ways(n-2). Basisgevallen: ways(0) = 1 (één manier om op de begane grond te blijven) en ways(1) = 1. Met memoization gebruik je O(n) tijd en O(n) ruimte.
import functools
@functools.lru_cache(maxsize=None)
def climbStairs(n):
if n <= 1:
return 1
return climbStairs(n-1) + climbStairs(n-2)
for i in range(1, 8):
print(f'climbStairs({i}) = {climbStairs(i)}')
# 1,2,3,5,8,13,21Wisselen met munten (LeetCode 322)
LeetCode 322 'Wisselen met munten': gegeven verschillende muntwaarden en een doelbedrag, vind je het minimale aantal munten. Top-down memoized recursie: dp(amount) = 1 + min(dp(amount - coin)) voor elke geldige munt. Het basisgeval is dp(0) = 0. Sla elk deelbedrag op in de cache. Als een deelbedrag onmogelijk is, retourneer je oneindig. Memoization verandert de exponentiële brute force in O(amount × len(coins)) tijd.
import functools
def coinChange(coins, amount):
@functools.lru_cache(maxsize=None)
def dp(rem):
if rem == 0:
return 0
if rem < 0:
return float('inf')
return 1 + min(dp(rem - c) for c in coins)
result = dp(amount)
return result if result != float('inf') else -1
print(coinChange([1, 5, 11], 15)) # 3 (5+5+5)
print(coinChange([1, 2, 5], 11)) # 3 (5+5+1)
print(coinChange([2], 3)) # -1Woorden splitsen (LeetCode 139) met memoization
LeetCode 139 'Woorden splitsen': bepaal of een tekenreeks kan worden opgesplitst in woorden uit een woordenboek. Top-down recursie: can_break(s, start) probeert elk voorvoegsel s[start:end]; als dit in het woordenboek staat en can_break(s, end) waar is, retourneer je true. Zonder memoization is dit O(2^n); met memoization (waarbij je elke startindex opslaat) wordt dit O(n² × L), waarbij L de maximale woordlengte is.
import functools
def wordBreak(s, wordDict):
word_set = set(wordDict)
@functools.lru_cache(maxsize=None)
def can_break(start):
if start == len(s):
return True
for end in range(start + 1, len(s) + 1):
if s[start:end] in word_set and can_break(end):
return True
return False
return can_break(0)
print(wordBreak('leetcode', ['leet', 'code'])) # True
print(wordBreak('applepenapple', ['apple','pen'])) # True
print(wordBreak('catsandog', ['cats','dog','sand','and','cat'])) # FalseMemoization versus tabulatie
Memoization (top-down) begint met het oorspronkelijke probleem en slaat antwoorden op zodra ze recursief worden gevonden. Alleen de deelproblemen die daadwerkelijk nodig zijn, worden opgelost. Tabulatie (van onder naar boven) vult vooraf een tabel in, van kleine naar grote deelproblemen, en lost alle deelproblemen op. Memoization is eenvoudiger af te leiden uit een recursieve oplossing; tabulatie vermijdt beperkingen van de recursiediepte en overhead van functieaanroepen.
# Memoisation (top-down)
import functools
@functools.lru_cache(maxsize=None)
def fib_td(n):
if n <= 1: return n
return fib_td(n-1) + fib_td(n-2)
# Tabulation (bottom-up)
def fib_bu(n):
if n <= 1: return n
dp = [0] * (n + 1)
dp[1] = 1
for i in range(2, n + 1):
dp[i] = dp[i-1] + dp[i-2]
return dp[n]
print(fib_td(20), fib_bu(20)) # 6765 6765
# Both O(n) time; fib_bu avoids recursion limitRuimte optimaliseren: voortschrijdende variabelen
Veel problemen met dynamische programmering die met memoized recursie O(n) ruimte gebruiken, kunnen verder worden geoptimaliseerd naar O(1) ruimte wanneer je slechts een vast aantal antwoorden van eerdere deelproblemen nodig hebt. Voor Fibonacci zijn alleen de laatste twee waarden van belang. Voor het beklimmen van trappen geldt hetzelfde. Met twee voortschrijdende variabelen vervang je het volledige memo-woordenboek of de volledige tabel.
# Fibonacci with O(1) space
def fib_o1(n):
if n <= 1:
return n
prev2, prev1 = 0, 1
for _ in range(2, n + 1):
prev2, prev1 = prev1, prev2 + prev1
return prev1
for i in range(8):
print(f'fib({i})={fib_o1(i)}', end=' ')
print()
# Climbing stairs O(1) space
def climbStairs_o1(n):
if n <= 1: return 1
a, b = 1, 1
for _ in range(2, n + 1):
a, b = b, a + b
return b
print(climbStairs_o1(10)) # 89lru_cache versus closure versus globaal woordenboek
Er zijn drie manieren om memoization handmatig te implementeren. Een globaal woordenboek is eenvoudig, maar vervuilt het bereik van de module. Een closure kapselt de cache in binnen de functie, waardoor lekken wordt voorkomen, maar vereist wel een wrapper. @lru_cache is het meest overzichtelijk — één decorator vervangt alle standaardcode. Begin in een interviewcontext met @lru_cache, tenzij de interviewer specifiek om een handmatige implementatie vraagt.
import functools
# 1. Global dict (messy)
memo_global = {}
def fib_global(n):
if n in memo_global: return memo_global[n]
if n <= 1: return n
memo_global[n] = fib_global(n-1) + fib_global(n-2)
return memo_global[n]
# 2. Closure (cleaner scope)
def make_fib():
cache = {}
def fib(n):
if n in cache: return cache[n]
if n <= 1: return n
cache[n] = fib(n-1) + fib(n-2)
return cache[n]
return fib
fib_closure = make_fib()
# 3. lru_cache (best)
@functools.lru_cache(maxsize=None)
def fib_cached(n):
if n <= 1: return n
return fib_cached(n-1) + fib_cached(n-2)
print(fib_global(30), fib_closure(30), fib_cached(30)) # all 832040Wanneer memoization niet helpt
Memoization versnelt alleen problemen met overlappende deelproblemen — gevallen waarin hetzelfde deelprobleem meerdere keren wordt berekend. Als elk deelprobleem uniek is (zoals bij een eenvoudige boomdoorloop waarbij elk knooppunt precies één keer wordt bezocht), voegt memoization overhead toe zonder voordeel. Bovendien kan memoization geen problemen oplossen waarbij de recursieve boom exponentieel is in het aantal verschillende deelproblemen in plaats van in het hergebruik — daarvoor is een volledig ander algoritme nodig.
# Memoisation DOES help: overlapping sub-problems (Fibonacci)
# fib(n) reuses fib(n-2), fib(n-3), etc.
# Memoisation does NOT help: distinct sub-problems (permutations)
# Each unique (remaining_elements, target) pair is truly distinct
# The exponential complexity comes from the state space itself
print('Memoisation: useful when SAME sub-problem recurs multiple times')
print('Not useful: when every sub-problem is unique to one recursive path')Samenvatting: checklist voor memoization
Pas memoization toe wanneer: je een recursieve oplossing hebt die correct maar traag is door onnodige herberekening, de functie een klein aantal verschillende combinaties van argumenten heeft en de retourwaarde alleen van de argumenten afhangt (een pure functie — zonder bijwerkingen en zonder globale toestand). Controleer de toestandsruimte van de deelproblemen: als er hoogstens O(n) of O(n²) verschillende toestanden zijn, verandert memoization exponentiële tijd in polynomiale tijd.
Korte controle
Test je begrip van de concepten van Data Structures & Algorithms — Coding Interview Prep uit deze les.
Samenvatting van de les
In deze les heb je geleerd: memoization slaat resultaten van deelproblemen op om herberekening te voorkomen en verandert exponentiële recursie in polynomiale tijd, @functools.lru_cache is het idiomatische Python-hulpmiddel waarvoor slechts één regel nodig is en memoization (top-down) en tabulatie (van onder naar boven) zijn de twee stijlen van dynamische programmering — memoization is eenvoudiger af te leiden en tabulatie voorkomt problemen met de stackdiepte. Gefeliciteerd — je hebt de modules over recursie en hash-maps voltooid!
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 “Memoisation: recursieve resultaten cachen” gratis?
Ja — je kunt hier op het web alle 3 lessen van het leerpad DSA Interview Prep, waaronder “Memoisation: recursieve resultaten cachen”, 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 “Memoisation: recursieve resultaten cachen”?
Pas @functools.lru_cache en handmatige memo-dicts toe op Fibonacci en climbing-stairs om exponentiële herberekening te elimineren. 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 4 van 4.
Hoe lang duurt de les “Memoisation: recursieve resultaten cachen”?
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
- Recursieframework: basisgeval, vertrouwen, opbouw
- De call stack visualiseren
- Afwegingen tussen recursief en iteratief
- Memoisation: recursieve resultaten cachen