Backtracking-Schema: Auswählen, Erkunden, Auswahl zurücknehmen
Implementieren Sie das dreistufige Backtracking-Grundgerüst, verfolgen Sie es an einem kleinen Beispiel und bestimmen Sie, an welchen Stellen Bedingungen zum Beschneiden eingefügt werden.
Backtracking-Schema: Auswählen, Erkunden, Auswahl zurücknehmen 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.
Was ist Backtracking?
Backtracking ist ein systematisches Verfahren, um alle (oder einige) Lösungen zu finden, indem alle Kandidaten schrittweise untersucht und Zweige aufgegeben (Pruning) werden, sobald feststeht, dass ein Zweig keine gültige Lösung liefern kann. Es ist der Algorithmus hinter dem Lösen von Sudoku, dem Erzeugen von Permutationen und dem Finden aller gültigen Kombinationen. Stellen Sie es sich als eine Tiefensuche in einem Entscheidungsbaum vor.
# Mental model: backtracking explores a decision tree
# At each node you make a choice, go deeper, then undo it
#
# Tree for generating subsets of [1,2,3]:
# []
# / \
# [1] []
# / \ / \
# [1,2][1][2] []
# ...
# Every leaf is a potential solution
# Pruning cuts branches early based on constraints
print('Backtracking = DFS on decision tree with pruning')Die Vorlage in drei Schritten
Jede Backtracking-Funktion umfasst drei Schritte: Auswählen — wählen Sie den nächsten Kandidaten aus den verfügbaren Optionen. Erkunden — rufen Sie die Funktion mit dieser Auswahl rekursiv auf und gehen Sie im Entscheidungsbaum eine Ebene tiefer. Rückgängigmachen — machen Sie die Auswahl nach der Rückkehr aus der Rekursion rückgängig, um den Zustand für den nächsten Kandidaten wiederherzustellen. Dieses Muster wird je nach Kontext auch add/recurse/remove oder mark/recurse/unmark genannt.
def backtrack(current_state, choices, results):
# Base case: is current_state a complete solution?
if is_complete(current_state):
results.append(list(current_state)) # record solution
return
for choice in choices:
if is_valid(choice, current_state): # pruning condition
# 1. CHOOSE
current_state.append(choice)
# 2. EXPLORE
backtrack(current_state, choices, results)
# 3. UNCHOOSE (backtrack)
current_state.pop()
# Placeholder functions — filled per problem
def is_complete(state): return True
def is_valid(choice, state): return TrueEinfachstes Beispiel: Alle Teilmengen
Erzeugen Sie alle Teilmengen von [1, 2, 3]. Bei jedem Index entscheiden Sie, ob Sie das Element einschließen oder ausschließen. Der Startindex wird nach jedem Aufruf weitergesetzt, damit vorherige Elemente nicht erneut betrachtet werden. Eine Prüfung von Einschränkungen ist nicht erforderlich — jeder Zwischenzustand ist gültig. Dadurch entstehen 2ⁿ Teilmengen. Der Schritt zum Rückgängigmachen ist path.pop() nach dem rekursiven Aufruf.
def subsets(nums):
result = []
def backtrack(start, path):
result.append(list(path)) # every state is a valid subset
for i in range(start, len(nums)):
path.append(nums[i]) # CHOOSE
backtrack(i + 1, path) # EXPLORE
path.pop() # UNCHOOSE
backtrack(0, [])
return result
print(subsets([1, 2, 3]))
# [[], [1], [1,2], [1,2,3], [1,3], [2], [2,3], [3]]Die Pruning-Bedingung erkennen
Die Stärke von Backtracking gegenüber Brute Force liegt im Pruning: Sie erkennen frühzeitig, dass ein unvollständiger Pfad nicht zu einer gültigen Lösung führen kann. Bei Combination Sum (Zielsumme mit einer Obergrenze) gilt: Sobald die laufende Summe das Ziel überschreitet, wird jeder tiefere Zweig nur noch größer — brechen Sie ihn daher sofort ab. Beim N-Damen-Problem überspringen Sie eine Spalte, wenn eine Dame bereits platzierte Damen angreift. Pruning verwandelt exponentielle Bäume in handhabbare Suchvorgänge.
def combination_sum(candidates, target):
result = []
candidates.sort() # sort enables early termination
def backtrack(start, path, remaining):
if remaining == 0:
result.append(list(path))
return
for i in range(start, len(candidates)):
c = candidates[i]
if c > remaining: break # PRUNE: sorted, so rest are bigger too
path.append(c) # CHOOSE
backtrack(i, path, remaining - c) # EXPLORE (reuse allowed)
path.pop() # UNCHOOSE
backtrack(0, [], target)
return result
print(combination_sum([2, 3, 6, 7], 7)) # [[2,2,3],[7]]Die Wiederherstellung des Zustands ist entscheidend
Ein häufiger Fehler beim Backtracking besteht darin, den Zustand vor der nächsten Iteration nicht vollständig wiederherzustellen. Wenn Sie eine veränderliche Datenstruktur (Liste, Menge oder Gitter) verwenden, muss jede während des Auswählens vorgenommene Änderung beim Rückgängigmachen umgekehrt werden. Wenn Sie beispielsweise ein Gitter verändern (etwa bei Sudoku oder Word Search), setzen Sie die Zelle nach dem rekursiven Aufruf wieder auf leer. Wird dies vergessen, bleibt der Zustand für benachbarte Zweige beschädigt.
# Bug: forgetting to unmark in word search
# Correct pattern for grid backtracking:
def word_search(board, word):
m, n = len(board), len(board[0])
def dfs(r, c, k):
if k == len(word): return True
if not (0<=r<m and 0<=c<n): return False
if board[r][c] != word[k]: return False
temp, board[r][c] = board[r][c], '#' # CHOOSE (mark visited)
found = any(dfs(r+dr, c+dc, k+1)
for dr, dc in [(0,1),(0,-1),(1,0),(-1,0)])
board[r][c] = temp # UNCHOOSE (restore cell)
return found
return any(dfs(r, c, 0) for r in range(m) for c in range(n))
board = [['A','B','C','E'],['S','F','C','S'],['A','D','E','E']]
print(word_search([row[:] for row in board], 'ABCCED')) # TrueDen Entscheidungsbaum nachverfolgen
Verfolgen Sie für Combination Sum mit [2, 3, 6, 7] und dem Ziel 7 den Baum: An der Wurzel probieren Sie 2. Von 2 aus probieren Sie erneut 2 (remaining=3). Von 2+2 aus probieren Sie erneut 2 (remaining=1). 2>1, also wenden Sie Pruning an. Probieren Sie 3: 3>1, also ebenfalls Pruning. Gehen Sie zurück. Von 2+2 aus probieren Sie 3 (remaining=3). 3 entspricht dem Rest: Speichern Sie [2,2,3]. Gehen Sie zurück und setzen Sie die Suche fort. Diese Nachverfolgung zeigt, wie Pruning Zweige beseitigt, bevor sie zu ungültigen Ergebnissen führen.
def combination_sum_trace(candidates, target):
result = []
candidates.sort()
def backtrack(start, path, remaining, depth):
indent = ' ' * depth
print(f'{indent}explore({path}, remaining={remaining})')
if remaining == 0:
result.append(list(path))
print(f'{indent}FOUND: {path}')
return
for i in range(start, len(candidates)):
c = candidates[i]
if c > remaining:
print(f'{indent}PRUNE at {c}')
break
path.append(c)
backtrack(i, path, remaining - c, depth + 1)
path.pop()
backtrack(0, [], target, 0)
return result
combination_sum_trace([2, 3, 6, 7], 7)Backtracking und Brute Force im Vergleich
Brute Force probiert alle möglichen vollständigen Lösungen aus und überprüft anschließend jede einzelne. Backtracking wendet Pruning während des Aufbaus an und vervollständigt ungültige Pfade nie. Beim N-Damen-Problem mit N=8 überprüft Brute Force 8^8 = 16 Millionen Platzierungen. Backtracking reduziert dies auf etwa 2.057 rekursive Aufrufe. Mit wachsendem N wird der Unterschied drastisch größer: Bei N=12 probiert Brute Force 8,9 Milliarden Platzierungen aus, während Backtracking nur einen Bruchteil des Baums untersucht.
# Compare call counts: brute force vs backtracking for permutations
import sys
calls_brute = [0]
calls_back = [0]
def brute_force_perms(nums):
from itertools import permutations
return list(permutations(nums))
def backtrack_perms(nums):
result = []
used = [False] * len(nums)
def bt(path):
calls_back[0] += 1
if len(path) == len(nums):
result.append(list(path))
return
for i, n in enumerate(nums):
if not used[i]:
used[i] = True
path.append(n)
bt(path)
path.pop()
used[i] = False
bt([])
return result
backtrack_perms([1,2,3,4])
print(f'Backtrack calls for 4 items: {calls_back[0]}')Sammeln oder frühzeitig zurückkehren
Backtracking-Probleme fallen in zwei Kategorien: alle Lösungen aufzählen (jeden vollständigen Pfad sammeln) oder eine beliebige Lösung finden (True zurückgeben, sobald ein Pfad erfolgreich ist). Beim Aufzählen fügen Sie jede vollständige Lösung einer Ergebnisliste hinzu. Bei der Suche nach einer beliebigen Lösung geben Sie unmittelbar aus dem rekursiven Aufruf True zurück und reichen diesen Wert nach oben weiter. any(backtrack(...)) oder if backtrack(...): return True implementiert dieses Kurzschlussverhalten.
# Enumerate all: collect in results list
def all_solutions(candidates):
results = []
def bt(path, remaining):
if remaining == 0:
results.append(list(path))
return
for c in candidates:
if c <= remaining:
path.append(c); bt(path, remaining - c); path.pop()
bt([], 5)
return results
# Find any one: return True on first success
def any_solution(candidates, target):
def bt(path, remaining):
if remaining == 0: return True
for c in candidates:
if c <= remaining:
path.append(c)
if bt(path, remaining - c): return True # short-circuit
path.pop()
return False
path = []
return bt(path, target), pathMemoisation mit Backtracking
Reines Backtracking untersucht jeden Pfad ohne Zwischenspeicherung, was in Ordnung ist, wenn alle Lösungen benötigt werden. Einige Backtracking-Probleme enthalten jedoch sich überschneidende Teilprobleme. Word Break II lässt sich beispielsweise mit Backtracking und Memoisation lösen: Speichern Sie die Liste der Sätze, die von jedem Startindex aus möglich sind. Dadurch wird das Backtracking im Worst Case von exponentieller zu polynomialer Laufzeit. Erkennen Sie wiederholte Teilprobleme, um diese hybride Methode anzuwenden.
from functools import lru_cache
def word_break_all(s, wordDict):
words = set(wordDict)
@lru_cache(maxsize=None)
def bt(start):
if start == len(s): return [''] # empty suffix
result = []
for end in range(start + 1, len(s) + 1):
word = s[start:end]
if word in words:
for rest in bt(end):
result.append(word if not rest else word + ' ' + rest)
return result
return bt(0)
print(word_break_all('catsanddog', ['cat','cats','and','sand','dog']))
# ['cat sand dog', 'cats and dog']Zeitkomplexität von Backtracking
Die Zeitkomplexität von Backtracking hängt von der Anzahl der Blätter im Entscheidungsbaum multipliziert mit dem Arbeitsaufwand pro Knoten ab. Für Teilmengen gilt: O(n × 2ⁿ). Für Permutationen: O(n × n!). Für Combination Sum: O(target/min_candidate ^ n) im Worst Case. Pruning verringert den konstanten Faktor, aber nicht die asymptotische Schranke. Wenn Sie im Vorstellungsgespräch nach der Komplexität gefragt werden, nennen Sie die Größe des Baums im Worst Case und erwähnen Sie, dass Pruning den Algorithmus in der Praxis normalerweise deutlich schneller macht.
# Complexity quick reference:
# Subsets of n elements: O(n * 2^n) - 2^n subsets, each copied in O(n)
# Permutations of n: O(n * n!) - n! perms, each copied in O(n)
# Combination sum (target T): O(T^n / n!) worst case without pruning
# N-Queens: O(n!) - prune reduces practical count
# For n=10 permutations: 10! = 3,628,800 paths
import math
n = 10
print(f'n={n}: n!={math.factorial(n):,} paths')
print(f'n={n}: 2^n={2**n:,} subsets')Backtracking-Probleme erkennen
Hinweise darauf, dass ein Problem Backtracking erfordert: (1) Alle finden oder alle erzeugen — etwa Kombinationen, Permutationen oder Teilmengen. (2) Das Problem umfasst das Platzieren von Elementen oder Personen unter bestimmten Einschränkungen (N-Damen-Problem, Sudoku). (3) Der Lösungsraum ist exponentiell, aber Einschränkungen eliminieren frühzeitig die meisten Zweige. (4) Sie müssen Pfade in einem Graphen oder Gitter untersuchen, die Zustände möglicherweise erneut besuchen. Wenn Sie diese Hinweise sehen, verwenden Sie die Vorlage zum Auswählen, Erkunden und Rückgängigmachen.
# Common backtracking problem types:
# 1. Subsets / Power set
# 2. Permutations (with/without duplicates)
# 3. Combinations (k from n, combination sum)
# 4. Grid path finding (word search, unique paths with visited tracking)
# 5. Constraint satisfaction (N-queens, Sudoku solver)
# 6. String partitioning (palindrome partition, word break all)
# Template reminder:
def backtrack(start, path):
# base case: add to results or return True
for choice in get_choices(start):
if is_valid(choice, path): # prune
path.append(choice) # choose
backtrack(start+1, path) # explore
path.pop() # unchoose
def get_choices(start): return []
def is_valid(c, p): return TrueKurzer Test
Testen Sie Ihr Verständnis der Konzepte von Data Structures & Algorithms — Coding Interview Prep aus dieser Lektion.
Zusammenfassung der Lektion
In dieser Lektion haben Sie gelernt: Die Backtracking-Vorlage umfasst drei Schritte — Auswählen, Erkunden und Rückgängigmachen —, die dem Hinzufügen einer Auswahl, dem rekursiven Aufruf und dem Entfernen der Auswahl entsprechen, Pruning-Bedingungen eliminieren Zweige frühzeitig und machen Backtracking im Vergleich zu Brute Force praktikabel, und der Zustand muss nach jedem rekursiven Aufruf vollständig wiederhergestellt werden, damit benachbarte Zweige nicht beschädigt werden. Als Nächstes wenden wir die Vorlage an, um alle Teilmengen und die Potenzmenge zu erzeugen.
Häufig gestellte Fragen
Ist die Lektion „Backtracking-Schema: Auswählen, Erkunden, Auswahl zurücknehmen“ kostenlos?
Ja — der vollständige Text von „Backtracking-Schema: Auswählen, Erkunden, Auswahl zurücknehmen“ 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 „Backtracking-Schema: Auswählen, Erkunden, Auswahl zurücknehmen“?
Implementieren Sie das dreistufige Backtracking-Grundgerüst, verfolgen Sie es an einem kleinen Beispiel und bestimmen Sie, an welchen Stellen Bedingungen zum Beschneiden eingefügt werden. 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 „Backtracking-Schema: Auswählen, Erkunden, Auswahl zurücknehmen“?
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
- Backtracking-Schema: Auswählen, Erkunden, Auswahl zurücknehmen
- Teilmengen und Potenzmenge
- Permutationen und Kombinationen
- N-Damen und Constraint Propagation