Backtracking-sjabloon: kiezen, verkennen, keuze ongedaan maken
Implementeer het skelet voor backtracking in drie stappen, doorloop het aan de hand van een klein voorbeeld en bepaal waar snoeivoorwaarden worden ingevoegd.
Backtracking-sjabloon: kiezen, verkennen, keuze ongedaan maken is een gratis Voorbereiding op programmeerinterviews-les op CoddyKit. Dit is les 1 van 4. Je kunt de volledige les hieronder gratis lezen en daarna in de browser praktisch oefenen met een ingebouwde code-editor en een AI-begeleider die 24/7 beschikbaar is. Deze les maakt deel uit van het leertraject Voorbereiding op programmeerinterviews. Je voortgang wordt gesynchroniseerd op het web en in de CoddyKit-app. De cursus Voorbereiding op programmeerinterviews bevat in totaal 4 lessen.
Wat is Backtracking?
Backtracking is een systematische methode om alle (of sommige) oplossingen te vinden door elke kandidaat stapsgewijs te onderzoeken en een tak af te breken (snoeien) zodra vaststaat dat die tak geen geldige oplossing kan opleveren. Dit is het algoritme achter het oplossen van Sudoku, het genereren van permutaties en het vinden van alle geldige combinaties. Zie het als een diepte-eerst zoeken in een beslisboom.
# 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')Het sjabloon in drie stappen
Elke Backtracking-functie volgt drie stappen: Kiezen — kies de volgende kandidaat uit de beschikbare opties. Verkennen — roep de functie recursief aan met die keuze en ga één niveau dieper in de beslisboom. Ongedaan maken — maak de keuze na terugkeer uit de recursie ongedaan om de toestand voor de volgende kandidaat te herstellen. Dit patroon wordt in verschillende contexten ook toevoegen/recursie/verwijderen of markeren/recursie/markering verwijderen genoemd.
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 TrueEenvoudigste voorbeeld: alle deelverzamelingen
Genereer alle deelverzamelingen van [1, 2, 3]. Bij elke index kiezen we of we het element opnemen of uitsluiten. De startindex schuift na elke aanroep op, zodat we eerdere elementen niet opnieuw bekijken. Er is geen controle van beperkingen nodig — elke gedeeltelijke toestand is geldig. Dit levert 2ⁿ deelverzamelingen op. De stap voor het ongedaan maken is path.pop() na de recursieve aanroep.
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]]De snoeivoorwaarde bepalen
De kracht van Backtracking ten opzichte van brute force zit in snoeien: vroeg herkennen dat een gedeeltelijk pad niet tot een geldige oplossing kan leiden. Bij combinatiesommen (een doelsom met een budget) geldt: zodra de lopende som groter is dan het doel, zal elke diepere tak alleen maar groter worden — snoei door onmiddellijk terug te keren. Bij N-koninginnen sla je een kolom over als een koningin bestaande koninginnen aanvalt. Snoeien verandert exponentiële bomen in hanteerbare zoekopdrachten.
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]]Herstel van de toestand is cruciaal
Een veelgemaakte fout bij Backtracking is dat de toestand niet volledig wordt hersteld vóór de volgende iteratie. Als je een veranderlijke gegevensstructuur gebruikt (lijst, verzameling, raster), moet elke wijziging die tijdens Kiezen is gemaakt tijdens Ongedaan maken worden teruggedraaid. Als je bijvoorbeeld een raster wijzigt (zoals bij Sudoku of Woordzoeker), zet je de cel na de recursieve aanroep terug op leeg. Als je dit vergeet, blijft de toestand beschadigd voor zustertakken.
# 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')) # TrueDe beslisboom volgen
Volg voor de combinatiesom met [2, 3, 6, 7] en doel 7 de boom: probeer bij de wortel 2. Probeer vanuit 2 opnieuw 2 (resterend=3). Probeer vanuit 2+2 opnieuw 2 (resterend=1). 2>1, dus snoei. Probeer 3: 3>1, dus snoei. Ga terug. Probeer vanuit 2+2 de 3 (resterend=3). 3 is gelijk aan het resterende bedrag: leg [2,2,3] vast. Ga terug en ga verder. Deze trace laat zien hoe snoeien takken verwijdert voordat ze ongeldige resultaten opleveren.
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 tegenover brute force
Brute force probeert alle mogelijke volledige oplossingen en controleert daarna elke oplossing. Backtracking snoeit tijdens het opbouwen en maakt ongeldige paden nooit af. Voor N-koninginnen met N=8 controleert brute force 8^8 = 16 miljoen plaatsingen. Backtracking vermindert dit tot ongeveer 2.057 recursieve aanroepen. Het verschil groeit sterk met N: voor N=12 probeert brute force 8,9 miljard plaatsingen, terwijl Backtracking slechts een fractie van de boom doorzoekt.
# 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]}')Verzamelen tegenover vroegtijdig terugkeren
Backtrackingproblemen vallen in twee categorieën: alle oplossingen opsommen (elk volledig pad verzamelen) of één oplossing vinden (True teruggeven zodra een pad slaagt). Voeg voor opsommingen altijd toe aan een resultatenlijst. Geef bij het zoeken naar één oplossing onmiddellijk True terug vanuit de recursieve aanroep en geef deze waarde naar boven door. any(backtrack(...)) of if backtrack(...): return True implementeert dit gedrag waarbij de rest wordt overgeslagen zodra het resultaat vaststaat.
# 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), pathMemoisatie met Backtracking
Pure Backtracking doorzoekt elk pad zonder resultaten op te slaan, wat prima is wanneer alle oplossingen nodig zijn. Sommige Backtrackingproblemen hebben echter overlappende deelproblemen. Word Break II kan bijvoorbeeld worden opgelost met Backtracking en memoisatie: sla de lijst met mogelijke zinnen vanaf elke startindex op. Zo verandert de exponentiële Backtracking in het slechtste geval in een algoritme met polynomiale tijdscomplexiteit. Herken wanneer deelproblemen terugkeren om deze hybride aanpak toe te passen.
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']Tijdscomplexiteit van Backtracking
De tijdscomplexiteit van Backtracking hangt af van het aantal bladeren in de beslisboom maal het werk per knooppunt. Voor deelverzamelingen: O(n × 2ⁿ). Voor permutaties: O(n × n!). Voor combinatiesommen: O(target/min_candidate ^ n) in het slechtste geval. Snoeien verkleint de constante factor, maar niet de asymptotische bovengrens. Geef in een sollicitatiegesprek over complexiteit de grootte van de boom in het slechtste geval en vermeld dat snoeien het algoritme in de praktijk meestal veel sneller maakt.
# 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')Backtrackingproblemen herkennen
Signalen dat een probleem Backtracking nodig heeft: (1) Vind alle of genereer alle combinaties, permutaties of deelverzamelingen. (2) Het probleem gaat over het plaatsen van items of mensen onder beperkingen (N-koninginnen, Sudoku). (3) De oplossingsruimte is exponentieel, maar beperkingen verwijderen de meeste takken vroeg. (4) Je moet paden in een graaf of raster onderzoeken waarin toestanden mogelijk opnieuw worden bezocht. Als je deze signalen ziet, gebruik dan het sjabloon Kiezen-Verkennen-Ongedaan maken.
# 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 TrueKorte 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: het Backtracking-sjabloon heeft drie stappen — kiezen, verkennen en ongedaan maken — die overeenkomen met een keuze toevoegen, recursief aanroepen en de keuze verwijderen, snoeivoorwaarden verwijderen takken vroegtijdig en maken Backtracking praktisch in vergelijking met brute force, en de toestand moet na elke recursieve aanroep volledig worden hersteld om beschadiging van zustertakken te voorkomen. Hierna passen we het sjabloon toe om alle deelverzamelingen en de machtsverzameling te genereren.
Leer Voorbereiding op programmeerinterviews 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
- 90
- Lessen
- 360
Veelgestelde vragen
Is de les “Backtracking-sjabloon: kiezen, verkennen, keuze ongedaan maken” gratis?
Ja — de volledige tekst van “Backtracking-sjabloon: kiezen, verkennen, keuze ongedaan maken” kun je hier gratis op het web lezen. Als je interactief wilt oefenen met een ingebouwde code-editor en een AI-begeleider die 24/7 beschikbaar is, en de rest van de cursus Voorbereiding op programmeerinterviews wilt ontgrendelen, kun je upgraden naar CoddyKit PRO. De cursus Voorbereiding op programmeerinterviews bevat in totaal 4 lessen.
Wat leer ik in “Backtracking-sjabloon: kiezen, verkennen, keuze ongedaan maken”?
Implementeer het skelet voor backtracking in drie stappen, doorloop het aan de hand van een klein voorbeeld en bepaal waar snoeivoorwaarden worden ingevoegd. Je oefent met Voorbereiding op programmeerinterviews 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 Voorbereiding op programmeerinterviews te beginnen?
Ervaring vooraf is niet nodig. Voorbereiding op programmeerinterviews 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 “Backtracking-sjabloon: kiezen, verkennen, keuze ongedaan maken”?
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 Voorbereiding op programmeerinterviews?
Ja. Elke les over Voorbereiding op programmeerinterviews 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
- Backtracking-sjabloon: kiezen, verkennen, keuze ongedaan maken
- Deelverzamelingen en machtsverzameling
- Permutaties en combinaties
- N-koninginnen en constraintpropagatie