Mal for backtracking: velg, utforsk, velg bort
Implementer skjelettet for tilbakesporing i tre trinn, spor det gjennom et lite eksempel, og identifiser hvor beskjæringsbetingelser skal settes inn.
Mal for backtracking: velg, utforsk, velg bort er en gratis leksjon i Forberedelse til kodeintervjuer på CoddyKit. Dette er leksjon 1 av 4. Du kan lese hele leksjonen gratis nedenfor – og deretter øve praktisk i nettleseren med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt. Den er en del av læringsløpet i Forberedelse til kodeintervjuer, og fremdriften din synkroniseres mellom nettet og CoddyKit-appen. Kurset i Forberedelse til kodeintervjuer inneholder totalt 4 leksjoner.
Hva er backtracking?
Backtracking er en systematisk metode for å finne alle (eller noen) løsninger ved å utforske hver kandidat trinnvis og avslutte (beskjære) en gren så snart det er klart at grenen ikke kan gi en gyldig løsning. Det er algoritmen som brukes til å løse Sudoku, generere permutasjoner og finne alle gyldige kombinasjoner. Tenk på det som et dybde-først-søk i et beslutningstre.
# 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')Den tredelte malen
Alle backtracking-funksjoner følger tre trinn: Velg — velg den neste kandidaten blant de tilgjengelige alternativene. Utforsk — kall rekursivt med dette valget, og gå ett nivå dypere i beslutningstreet. Angre valg — opphev valget etter retur fra rekursjonen for å gjenopprette tilstanden for neste kandidat. Dette mønsteret kalles også legg til/rekursjon/fjern eller marker/rekursjon/fjern markering i ulike sammenhenger.
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 TrueEnkleste eksempel: alle delmengder
Generer alle delmengder av [1, 2, 3]. Ved hver indeks velges det om elementet skal inkluderes eller utelates. Startindeksen økes etter hvert kall, slik at tidligere elementer ikke besøkes på nytt. Det trengs ingen begrensningskontroll — enhver delvis tilstand er gyldig. Dette gir 2ⁿ delmengder. Trinnet for å angre valget er path.pop() etter det rekursive kallet.
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]]Identifisere beskjæringsbetingelsen
Styrken ved backtracking sammenlignet med brute force ligger i beskjæring: å oppdage tidlig at en delvis sti ikke kan føre til en gyldig løsning. For kombinasjonssum (målsum med et budsjett) vil enhver dypere gren bare bli større når den løpende summen først overstiger målet — beskjær ved å returnere umiddelbart. For N-dronninger: hopp over kolonnen hvis en dronning angriper eksisterende dronninger. Beskjæring gjør eksponentielle trær til håndterbare søk.
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]]Gjenoppretting av tilstand er avgjørende
En vanlig feil i backtracking er at tilstanden ikke gjenopprettes fullstendig før neste iterasjon. Hvis det brukes en muterbar datastruktur (liste, mengde eller rutenett), må enhver endring som gjøres under Velg, reverseres under Angre valg. Ved endring av et rutenett (som i Sudoku eller Word Search) må cellen for eksempel settes tilbake til tom etter det rekursive kallet. Hvis dette glemmes, blir tilstanden ødelagt for søskengrenene.
# 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')) # TrueSporing av beslutningstreet
For kombinasjonssum med [2, 3, 6, 7] og målet 7 kan treet spores slik: Ved roten prøves 2. Fra 2 prøves 2 igjen (remaining=3). Fra 2+2 prøves 2 igjen (remaining=1). 2>1, så grenen beskjæres. Prøv 3: 3>1, så grenen beskjæres. Gå tilbake. Fra 2+2 prøves 3 (remaining=3). 3 tilsvarer den gjenværende verdien: registrer [2,2,3]. Gå tilbake og fortsett. Denne sporingen viser hvordan beskjæring fjerner grener før de produserer ugyldige resultater.
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 kontra brute force
Brute force prøver alle mulige komplette løsninger og validerer deretter hver av dem. Backtracking beskjærer under konstruksjonen og fullfører aldri ugyldige stier. For N-dronninger med N=8 kontrollerer brute force 8^8 = 16 millioner plasseringer. Backtracking reduserer dette til rundt 2 057 rekursive kall. Forskjellen blir dramatisk større når N øker: For N=12 prøver brute force 8,9 milliarder plasseringer, mens backtracking bare utforsker en brøkdel av treet.
# 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]}')Samle inn kontra tidlig retur
Backtracking-problemer faller i to kategorier: enumerer alle løsninger (samle inn hver fullstendige sti) eller finn én vilkårlig løsning (returner True så snart en sti lykkes). Ved enumerering skal resultatlisten alltid utvides. Ved søk etter én løsning returneres True umiddelbart fra det rekursive kallet, og dette videreføres oppover. any(backtrack(...)) eller if backtrack(...): return True implementerer denne kortslutningsatferden.
# 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), pathMemoisering med backtracking
Ren backtracking utforsker hver sti uten hurtigbufring, noe som er passende når alle løsninger trengs. Noen backtracking-problemer har imidlertid overlappende delproblemer. Word Break II kan for eksempel løses med backtracking + memoisering: mellomlagre listen over setninger som er mulige fra hver startindeks. Dette gjør backtracking-algoritmen med eksponentiell verstetid om til en algoritme med polynomisk tid. Gjenkjenn når delproblemer gjentas, slik at denne kombinasjonen kan brukes.
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']Tidskompleksiteten til backtracking
Tidskompleksiteten til backtracking avhenger av antallet bladnoder i beslutningstreet multiplisert med arbeidet per node. For delmengder: O(n × 2ⁿ). For permutasjoner: O(n × n!). For kombinasjonssum: O(target/min_candidate ^ n) i verste fall. Beskjæring reduserer konstantleddet, men ikke den asymptotiske grensen. Når kompleksiteten etterspørres i et intervju, oppgi treets størrelse i verste fall og nevn at beskjæring vanligvis gjør algoritmen langt raskere i praksis.
# 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')Identifisere backtracking-problemer
Tegn på at et problem trenger backtracking: (1) Det skal finnes alle eller genereres alle kombinasjoner, permutasjoner eller delmengder. (2) Problemet innebærer å plassere elementer eller personer under begrensninger (N-dronninger, Sudoku). (3) Løsningsrommet er eksponentielt, men begrensningene eliminerer de fleste grenene tidlig. (4) Det er nødvendig å utforske stier i en graf eller et rutenett der tilstander kan besøkes på nytt. Når disse tegnene vises, bør malen Velg–utforsk–angre valg brukes.
# 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 TrueHurtigsjekk
Test forståelsen av Data Structures & Algorithms — Coding Interview Prep-konseptene fra denne leksjonen.
Oppsummering av leksjonen
I denne leksjonen har man lært: backtracking-malen har tre trinn — velg, utforsk og angre valg — som tilsvarer å legge til et valg, kalle rekursivt og fjerne valget, beskjæringsbetingelser eliminerer grener tidlig og er det som gjør backtracking praktisk sammenlignet med brute force, og tilstanden må gjenopprettes fullstendig etter hvert rekursive kall for å unngå å ødelegge søskengrenene. Neste tema er å bruke malen til å generere alle delmengder og potensmengden.
Lær deg Forberedelse til kodeintervjuer med en AI-veileder – gratis
Skriv og kjør ekte kode i nettleseren, få umiddelbar hjelp fra en AI-veileder som er tilgjengelig døgnet rundt, og fortsett der du slapp – på nettet eller i appen.
- Kurs
- 90
- Leksjoner
- 360
Ofte stilte spørsmål
Er leksjonen «Mal for backtracking: velg, utforsk, velg bort» gratis?
Ja – hele teksten i «Mal for backtracking: velg, utforsk, velg bort» er gratis å lese her på nettet. For å øve interaktivt med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt, og for å låse opp resten av Forberedelse til kodeintervjuer-kurset, kan du oppgradere til CoddyKit PRO. Kurset i Forberedelse til kodeintervjuer inneholder totalt 4 leksjoner.
Hva lærer jeg i «Mal for backtracking: velg, utforsk, velg bort»?
Implementer skjelettet for tilbakesporing i tre trinn, spor det gjennom et lite eksempel, og identifiser hvor beskjæringsbetingelser skal settes inn. Du øver på Forberedelse til kodeintervjuer med praktisk kode som du kjører direkte i nettleseren, mens en AI-veileder som er tilgjengelig døgnet rundt, svarer på spørsmålene dine mens du jobber deg gjennom leksjonen.
Trenger jeg erfaring for å begynne med Forberedelse til kodeintervjuer?
Ingen tidligere erfaring er nødvendig. Forberedelse til kodeintervjuer på CoddyKit er lagt opp for både nybegynnere og viderekomne, så De kan begynne her eller helt fra start og lære i Deres eget tempo. Dette er leksjon 1 av 4.
Hvor lang tid tar leksjonen «Mal for backtracking: velg, utforsk, velg bort»?
De fleste CoddyKit-leksjoner tar omtrent 5–10 minutter. Hver leksjon er kort og interaktiv, slik at De gjør jevne fremskritt og kan fortsette akkurat der De slapp – både på nettet og i appen.
Kan jeg skrive og kjøre kode i denne Forberedelse til kodeintervjuer-leksjonen?
Ja. Alle Forberedelse til kodeintervjuer-leksjoner har en innebygd kodeeditor, slik at De kan skrive og kjøre ekte kode direkte i nettleseren og få umiddelbar tilbakemelding fra AI – uten lokal konfigurering.
Alle leksjonene i dette kurset
- Mal for backtracking: velg, utforsk, velg bort
- Delmengder og potensmengde
- Permutasjoner og kombinasjoner
- N-dronninger og begrensningspropagering