Memoisation: Rekursive Ergebnisse zwischenspeichern
Wenden Sie @functools.lru_cache und manuelle Memo-Dictionaries auf Fibonacci und climbing-stairs an, um exponentielle Neuberechnungen zu vermeiden.
Memoisation: Rekursive Ergebnisse zwischenspeichern 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.
Das Problem redundanter Rekursion
Naive rekursive Fibonacci berechnet dieselben Werte wiederholt. fib(5) ruft fib(4) und fib(3) auf; fib(4) ruft fib(3) und fib(2) auf – daher wird fib(3) zweimal berechnet. Diese Redundanz wächst exponentiell: fib(40) führt zu mehr als einer Milliarde Funktionsaufrufen. Memoisierung löst dieses Problem, indem sie jedes Ergebnis beim ersten Berechnen speichert, sodass nachfolgende Aufrufe es in O(1) abrufen, statt es erneut zu berechnen.
# 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=40Manuelle Memoisierung mit einem Dict
Fügen Sie ein memo-Dict als Parameter (oder in einer Closure) hinzu. Überprüfen Sie vor der Berechnung, ob sich die Antwort bereits in memo befindet. Falls ja, geben Sie sie sofort zurück. Falls nein, berechnen Sie sie, speichern Sie sie in memo und geben Sie sie zurück. Jedes eindeutige Teilproblem wird nun genau einmal berechnet, wodurch aus O(2^n) eine Laufzeit von O(n) und ein Speicherbedarf von O(n) für das memo-Dict sowie O(n) Stack-Speicher werden.
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!Der Decorator functools.lru_cache
Python stellt @functools.lru_cache(maxsize=None) (in Python 3.9+ auch als @functools.cache verfügbar) bereit, um die Memoisierung zu automatisieren. Wenn Sie diesen Decorator über einer Funktion hinzufügen, werden alle Aufrufe anhand ihrer Argumente zwischengespeichert. maxsize=None bedeutet eine unbegrenzte Cache-Größe – jede eindeutige Argumentkombination wird zwischengespeichert. Dadurch wird jede rekursive Funktion mit nur einer Codezeile in eine memoiserte Variante umgewandelt.
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=...)Treppensteigen (LeetCode 70)
LeetCode 70 „Climbing Stairs“: Sie können jeweils 1 oder 2 Stufen steigen. Wie viele Wege gibt es, Stufe n zu erreichen? Dies ist Fibonacci in einer anderen Form: ways(n) = ways(n-1) + ways(n-2). Basisfälle: ways(0) = 1 (eine Möglichkeit, auf der Ausgangsstufe zu bleiben), ways(1) = 1. Mit Memoisierung beträgt die Laufzeit O(n) und der Speicherbedarf O(n).
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,21Münzwechsel (LeetCode 322)
LeetCode 322 „Coin Change“: Gegeben sind Münzwerte und ein Zielbetrag; finden Sie die minimale Anzahl an Münzen. Top-down-Memoisierung mit Rekursion: dp(amount) = 1 + min(dp(amount - coin)) für jede gültige Münze. Der Basisfall ist: dp(0) = 0. Speichern Sie jeden Teilbetrag im Cache. Wenn ein Teilbetrag nicht erreichbar ist, geben Sie Unendlich zurück. Durch Memoisierung wird aus der exponentiellen Brute-Force-Lösung eine Laufzeit von O(amount × len(coins)).
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)) # -1Word Break (LeetCode 139) mit Memoisierung
LeetCode 139 „Word Break“: Bestimmen Sie, ob sich ein String in Wörter aus einem Wörterbuch zerlegen lässt. Die Top-down-Rekursion can_break(s, start) probiert jedes Präfix s[start:end] aus; wenn es im Wörterbuch enthalten ist und can_break(s, end) den Wert true liefert, geben Sie true zurück. Ohne Memoisierung beträgt die Laufzeit O(2^n); mit Memoisierung, bei der jeder Startindex im Cache gespeichert wird, wird sie zu O(n² × L), wobei L die maximale Wortlänge ist.
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'])) # FalseMemoisierung vs. Tabulation
Memoisierung (Top-down) beginnt beim ursprünglichen Problem und speichert Antworten, sobald sie bei der rekursiven Lösung entdeckt werden. Sie löst nur die Teilprobleme, die tatsächlich benötigt werden. Tabulation (Bottom-up) füllt eine Tabelle von kleinen zu großen Teilproblemen vorab aus und löst alle Teilprobleme, unabhängig davon, ob sie benötigt werden. Memoisierung lässt sich leichter aus einer rekursiven Lösung ableiten; Tabulation vermeidet Begrenzungen der Rekursionstiefe und den Overhead von Funktionsaufrufen.
# 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 limitSpeicheroptimierung: Laufende Variablen
Viele DP-Probleme, die durch memoiserte Rekursion O(n) Speicherplatz benötigen, lassen sich auf O(1) Speicherplatz optimieren, wenn nur eine feste Anzahl vorheriger Antworten auf Teilprobleme benötigt wird. Bei Fibonacci sind nur die letzten beiden Werte relevant. Beim Treppensteigen gilt dasselbe. Zwei laufend aktualisierte Variablen ersetzen das gesamte memo-Dict oder die gesamte Tabelle.
# 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 vs. Closure vs. globales Dict
Es gibt drei Möglichkeiten, Memoisierung manuell zu implementieren. Ein globales Dict ist einfach, belastet aber den Modul-Scope. Eine Closure kapselt den Cache innerhalb der Funktion und verhindert Leaks, erfordert jedoch einen Wrapper. @lru_cache ist die sauberste Lösung – ein einziger Decorator ersetzt den gesamten Boilerplate-Code. Beginnen Sie im Interviewkontext mit @lru_cache, es sei denn, der Interviewer bittet ausdrücklich um eine manuelle Implementierung.
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 832040Wann Memoisierung nicht hilft
Memoisierung beschleunigt nur Probleme mit überlappenden Teilproblemen – Fälle, in denen dasselbe Teilproblem mehrfach berechnet wird. Wenn jedes Teilproblem eindeutig ist (wie bei einer einfachen Baumtraversierung, bei der jeder Knoten genau einmal besucht wird), verursacht Memoisierung zusätzlichen Overhead ohne Nutzen. Außerdem kann Memoisierung keine Probleme lösen, bei denen der rekursive Baum exponentiell in der Anzahl der unterschiedlichen Teilprobleme wächst und nicht durch die Wiederverwendung von Teilproblemen entsteht – dafür ist ein völlig anderer Algorithmus erforderlich.
# 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')Zusammenfassung: Checkliste zur Memoisierung
Wenden Sie Memoisierung an, wenn eine rekursive Lösung zwar korrekt, aber wegen redundanter Neuberechnungen langsam ist, die Funktion nur wenige unterschiedliche Argumentkombinationen besitzt und der Rückgabewert ausschließlich von den Argumenten abhängt (reine Funktion – keine Seiteneffekte, kein globaler Zustand). Prüfen Sie den Zustandsraum der Teilprobleme: Bei höchstens O(n) oder O(n²) unterschiedlichen Zuständen wandelt Memoisierung exponentielle in polynomiale Laufzeit um.
Schnelltest
Überprüfen Sie Ihr Verständnis der Konzepte aus Data Structures & Algorithms — Coding Interview Prep in dieser Lektion.
Zusammenfassung der Lektion
In dieser Lektion haben Sie gelernt: Memoisierung speichert die Ergebnisse von Teilproblemen, um Neuberechnungen zu vermeiden, und wandelt exponentielle Rekursion in polynomiale Laufzeit um, @functools.lru_cache ist das idiomatische Python-Werkzeug und benötigt nur eine Zeile und Memoisierung (Top-down) und Tabulation (Bottom-up) sind die beiden DP-Varianten – Memoisierung lässt sich leichter ableiten, während Tabulation Probleme mit der Stack-Tiefe vermeidet. Herzlichen Glückwunsch – Sie haben die Module zu Rekursion und Hash-Maps abgeschlossen!
Häufig gestellte Fragen
Ist die Lektion „Memoisation: Rekursive Ergebnisse zwischenspeichern“ kostenlos?
Ja — der vollständige Text von „Memoisation: Rekursive Ergebnisse zwischenspeichern“ 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 „Memoisation: Rekursive Ergebnisse zwischenspeichern“?
Wenden Sie @functools.lru_cache und manuelle Memo-Dictionaries auf Fibonacci und climbing-stairs an, um exponentielle Neuberechnungen zu vermeiden. 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 „Memoisation: Rekursive Ergebnisse zwischenspeichern“?
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
- Rekursionsschema: Basisfall, Vertrauen, Aufbau
- Den Aufrufstapel visualisieren
- Abwägungen zwischen rekursiv und iterativ
- Memoisation: Rekursive Ergebnisse zwischenspeichern