House Robber: Rekurrenz aus Nehmen oder Überspringen
Modellieren Sie die Entscheidung zwischen Stehlen und Überspringen als DP-Rekurrenz, reduzieren Sie den Speicher auf zwei Variablen und erweitern Sie die Lösung auf kreisförmig angeordnete Häuser.
House Robber: Rekurrenz aus Nehmen oder Überspringen ist eine kostenlose Coding 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 Coding Interview Prep-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der Coding Interview Prep-Kurs umfasst insgesamt 4 Lektionen.
Das House-Robber-Problem
Das Problem House Robber lautet: Gegeben sei ein Array nichtnegativer Ganzzahlen, das den Geldbetrag in jedem Haus darstellt. Finden Sie den maximalen Betrag, den Sie stehlen können, ohne zwei benachbarte Häuser auszurauben. Zum Beispiel ergibt [2, 7, 9, 3, 1] den Wert 12, wenn Sie die Häuser 0, 2 und 4 ausrauben. Dies ist ein klassisches 1D-DP-Problem, bei dem Sie an jeder Position eine binäre Entscheidung treffen.
nums = [2, 7, 9, 3, 1]
# Can't rob adjacent houses
# Options: rob index 0 and 2 and 4 → 2+9+1=12
# or rob index 1 and 3 → 7+3=10
print('Max profit:', 12) # answer is 12Die Rekurrenz definieren
Sei dp[i] der maximale Geldbetrag, den Sie aus den ersten i+1 Häusern stehlen können. Bei jedem Haus i haben Sie zwei Möglichkeiten: es überspringen und dp[i-1] übernehmen oder es ausrauben und nums[i] + dp[i-2] übernehmen. Die Rekurrenz lautet dp[i] = max(dp[i-1], nums[i] + dp[i-2]). Dies ist das grundlegende Take-or-Skip-Muster, das in vielen DP-Problemen vorkommt.
# Recurrence: dp[i] = max(dp[i-1], nums[i] + dp[i-2])
# Base cases:
# dp[0] = nums[0] (only one house, rob it)
# dp[1] = max(nums[0], nums[1]) (take the richer of the two)
def rob(nums):
n = len(nums)
if n == 1: return nums[0]
dp = [0] * n
dp[0] = nums[0]
dp[1] = max(nums[0], nums[1])
for i in range(2, n):
dp[i] = max(dp[i-1], nums[i] + dp[i-2])
return dp[-1]
print(rob([2, 7, 9, 3, 1])) # 12Die DP-Tabelle nachvollziehen
Für [2, 7, 9, 3, 1] können wir die Tabelle nachvollziehen: dp[0] = 2, dp[1] = max(2, 7) = 7, dp[2] = max(7, 9+2) = 11, dp[3] = max(11, 3+7) = 11, dp[4] = max(11, 1+11) = 12. Die endgültige Antwort ist dp[4] = 12. Wenn Sie die Tabelle manuell nachvollziehen, können Sie überprüfen, dass die Rekurrenz an jeder Position sowohl das Auswählen als auch das Überspringen korrekt behandelt.
nums = [2, 7, 9, 3, 1]
dp = [0] * len(nums)
dp[0] = 2
dp[1] = max(2, 7) # 7
for i in range(2, len(nums)):
skip = dp[i-1]
take = nums[i] + dp[i-2]
dp[i] = max(skip, take)
print(f'dp[{i}] = max({skip}, {nums[i]}+{dp[i-2]}) = {dp[i]}')
print('Answer:', dp[-1])Speicher auf O(1) reduzieren
Die DP-Tabelle greift nur auf die zwei vorherigen Positionen zurück. Daher können wir das gesamte Array durch zwei Variablen ersetzen: prev2 für den Wert zwei Schritte zurück und prev1 für den Wert einen Schritt zurück. Nach jeder Iteration verschieben wir sie: prev2 = prev1 und prev1 = current. Dadurch reduzieren wir den Speicherbedarf von O(n) auf O(1), während die Zeitkomplexität bei O(n) bleibt.
def rob_optimised(nums):
if not nums: return 0
if len(nums) == 1: return nums[0]
prev2 = nums[0]
prev1 = max(nums[0], nums[1])
for i in range(2, len(nums)):
curr = max(prev1, nums[i] + prev2)
prev2 = prev1
prev1 = curr
return prev1
print(rob_optimised([2, 7, 9, 3, 1])) # 12
print(rob_optimised([1, 2, 3, 1])) # 4Zu behandelnde Sonderfälle
Testen Sie Ihre Lösung immer mit Sonderfällen: einem leeren Array, für das 0 zurückgegeben wird, einem Array mit einem Element, für das dieses Element zurückgegeben wird, und einem Array mit zwei Elementen, für das das Maximum der beiden zurückgegeben wird. In Interviews zeigt das Ansprechen und Behandeln dieser Fälle, dass Sie gründlich arbeiten. Die Abfrage if n == 1 verhindert einen Indexfehler beim Zugriff auf nums[1] für dp[1].
def rob(nums):
if not nums: return 0
if len(nums) == 1: return nums[0]
prev2 = nums[0]
prev1 = max(nums[0], nums[1])
for i in range(2, len(nums)):
curr = max(prev1, nums[i] + prev2)
prev2, prev1 = prev1, curr
return prev1
print(rob([])) # 0
print(rob([5])) # 5
print(rob([3, 10])) # 10
print(rob([10, 3])) # 10House Robber II: Häuser im Kreis
Die kreisförmige Variante (LeetCode 213) ordnet die Häuser in einem Kreis an, sodass das erste und das letzte Haus benachbart sind. Sie können die lineare Rekurrenz nicht direkt anwenden. Die entscheidende Erkenntnis lautet: Entweder rauben Sie das erste Haus aus und schließen das letzte aus, oder Sie schließen das erste aus und nehmen das letzte auf. Führen Sie House Robber für beide Teil-Arrays aus und wählen Sie das Maximum.
def rob_linear(nums):
prev2, prev1 = 0, 0
for n in nums:
prev2, prev1 = prev1, max(prev1, n + prev2)
return prev1
def rob_circular(nums):
if len(nums) == 1: return nums[0]
# Either include first (exclude last) or include last (exclude first)
return max(rob_linear(nums[:-1]), rob_linear(nums[1:]))
print(rob_circular([2, 3, 2])) # 3
print(rob_circular([1, 2, 3, 1])) # 4Warum Greedy hier scheitert
Ein naiver Greedy-Ansatz könnte versuchen, immer das jeweils wertvollste verfügbare Haus auszurauben. Das scheitert jedoch bei Eingaben wie [2, 1, 1, 2]: Greedy wählt Haus 0 mit dem Wert 2 und anschließend Haus 3 mit dem Wert 2, also insgesamt 4. Das Ausrauben der Häuser 0 und 2 ergibt dagegen ebenfalls 3. Moment — in diesem Fall funktioniert Greedy! Betrachten Sie stattdessen [1, 3, 1, 3, 100]: Greedy wählt die 3 an den Indizes 1 und 3 und erhält 6, verpasst aber das Optimum 1+1+100=102. DP ist erforderlich, weil lokal optimale Entscheidungen kein globales Optimum garantieren.
# Greedy failure example
nums = [1, 3, 1, 3, 100]
# Greedy: pick max each step
# picks 3 (index 1), then 3 (index 3) → total 6
# DP optimal: pick 1 (index 0) + 1 (index 2) + 100 (index 4) → 102
def rob(nums):
prev2, prev1 = 0, 0
for n in nums:
prev2, prev1 = prev1, max(prev1, n + prev2)
return prev1
print(rob(nums)) # 102Das Take-or-Skip-Muster erkennen
Das Take-or-Skip-Muster lässt sich über House Robber hinaus verallgemeinern. Immer wenn Sie ein Array durchlaufen und an jeder Position zwischen dem Aufnehmen des aktuellen Elements — wobei das vorherige übersprungen wird — und dem Ausschließen des aktuellen Elements — wobei das bisherige Ergebnis beibehalten wird — wählen, handelt es sich um eine Take-or-Skip-DP. Achten Sie auf Einschränkungen wie keine zwei benachbarten Elemente oder keine überlappenden Intervalle; sie sind Hinweise darauf, dass dieses Muster angewendet werden kann.
# General take-or-skip template
def take_or_skip(values, gap=1):
'''Max sum where selected elements must be at least gap+1 apart.'''
n = len(values)
if n == 0: return 0
# dp[i] = best up to index i
dp = [0] * (n + gap)
for i in range(n):
take = values[i] + (dp[i - 1] if i >= 1 else 0)
skip = dp[i + gap - 1] if i + gap - 1 < len(dp) else 0
dp[i + gap] = max(skip, take)
return dp[-1]
print(take_or_skip([2, 7, 9, 3, 1])) # house robber-likeDie Variante Delete and Earn
Delete and Earn (LeetCode 740) fragt: Für jede ausgewählte Zahl erhalten Sie num × count(num), müssen aber alle Vorkommen von num-1 und num+1 löschen. Das lässt sich direkt auf House Robber reduzieren: Erstellen Sie für alle Werte ein Array mit earn[v] = v × count(v) und führen Sie anschließend House Robber auf diesem Array aus. Solche Reduktionen zu erkennen, ist eine wichtige Fähigkeit für Bewerbungsgespräche.
from collections import Counter
def delete_and_earn(nums):
if not nums: return 0
count = Counter(nums)
max_val = max(nums)
# earn[v] = total points from taking all v's
earn = [v * count[v] for v in range(max_val + 1)]
# Now run house robber on earn
prev2, prev1 = 0, 0
for e in earn:
prev2, prev1 = prev1, max(prev1, e + prev2)
return prev1
print(delete_and_earn([3, 4, 2])) # 6 (take 3+3=no, take 4+2=6)
print(delete_and_earn([2, 2, 3, 3, 3, 4])) # 9 (take all 3s)House Robber III: Binärbaum
Bei House Robber III sind die Häuser als Binärbaum angeordnet. Sie können einen Knoten und seinen direkten Elternknoten nicht gleichzeitig ausrauben. Definieren Sie eine Hilfsfunktion, die zwei Werte zurückgibt: rob(node) → (rob_root, skip_root). Wenn Sie die Wurzel ausrauben, addieren Sie die Skip-Werte beider Kindknoten. Wenn Sie die Wurzel überspringen, addieren Sie für jedes Kind den jeweils besseren Wert. Dies ist eine Postorder-DFS mit einer Take-or-Skip-Entscheidung an jedem Knoten.
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def rob_tree(root):
def dfs(node):
if not node: return (0, 0) # (rob, skip)
l_rob, l_skip = dfs(node.left)
r_rob, r_skip = dfs(node.right)
rob = node.val + l_skip + r_skip
skip = max(l_rob, l_skip) + max(r_rob, r_skip)
return (rob, skip)
return max(dfs(root))
# Tree: 3 -> 2,3 -> None,3,None,1
root = TreeNode(3, TreeNode(2, None, TreeNode(3)), TreeNode(3, None, TreeNode(1)))
print(rob_tree(root)) # 7Komplexität und Diskussion im Interview
House Robber in der linearen Variante läuft mit der Optimierung auf zwei Variablen in O(n) Zeit und mit O(1) Speicher. Die kreisförmige Variante läuft ebenfalls in O(n) Zeit, da sie die lineare Variante zweimal aufruft. Die Baumvariante läuft in O(n) Zeit und benötigt O(h) Speicher, wobei h die Höhe des Baums ist. Geben Sie in einem Interview nach dem Programmieren immer die Komplexität an und erwähnen Sie die Speicheroptimierung — das zeigt, dass Sie über eine erste funktionierende Lösung hinausdenken.
# Summary of complexities
# Linear House Robber:
# Time: O(n), Space: O(1) with two-variable trick
# Circular House Robber:
# Time: O(n), Space: O(1) (two passes)
# Tree House Robber:
# Time: O(n), Space: O(h) call stack
# Quick benchmark
import time
import random
nums = [random.randint(0, 100) for _ in range(10**6)]
start = time.time()
prev2 = prev1 = 0
for n in nums:
prev2, prev1 = prev1, max(prev1, n + prev2)
print(f'1M elements in {time.time()-start:.3f}s, result={prev1}')Kurzer Test
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 Folgendes gelernt: die Take-or-Skip-Rekurrenz dp[i] = max(dp[i-1], nums[i] + dp[i-2]), den O(n)-Speicherbedarf mithilfe von zwei rollierenden Variablen auf O(1) zu reduzieren und das Muster auf Kreisarrays und Binärbäume zu übertragen. Als Nächstes untersuchen wir die Probleme Maximum Subarray und Maximum Product Subarray mithilfe des Kadane-Algorithmus.
Häufig gestellte Fragen
Ist die Lektion „House Robber: Rekurrenz aus Nehmen oder Überspringen“ kostenlos?
Ja — der vollständige Text von „House Robber: Rekurrenz aus Nehmen oder Überspringen“ 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 „House Robber: Rekurrenz aus Nehmen oder Überspringen“?
Modellieren Sie die Entscheidung zwischen Stehlen und Überspringen als DP-Rekurrenz, reduzieren Sie den Speicher auf zwei Variablen und erweitern Sie die Lösung auf kreisförmig angeordnete Häuser. 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 1 von 4.
Wie lange dauert die Lektion „House Robber: Rekurrenz aus Nehmen oder Überspringen“?
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
- House Robber: Rekurrenz aus Nehmen oder Überspringen
- Maximales Teilarray und Teilarray mit maximalem Produkt
- Word Break und String segmentieren
- Decode Ways und Pfade zählen