Files
leetcode/apps/docs/content/(05-decision-space)/backtracking/index.mdx
T

164 lines
4.7 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: Backtracking
description: 'Backtracking is a depth-first search technique that builds a solution step by step and abandons (undoes) a partial choice as soon as it can’t lead to a valid complete solution.'
hidden: true
---
## What it is
Backtracking explores possible paths to a solution one step at a time. When a path reaches a dead end, you undo the last step and try a different option. Think of every problem as a decision tree. The root is an empty answer. Each level is a decision. Each branch is one option.
## When to use it
Use backtracking when you must generate **all** valid solutions or evaluate **all** combinations. Do not use it when you need only one solution or the optimal solution. For those, use greedy or dynamic programming. Backtracking has exponential time complexity.
## The four components
Every backtracking problem has the same parts:
1. **Base case** — when to save the current path and stop.
2. **Choices** — the options at each step.
3. **Constraints** — the rules that reject an invalid path early.
4. **Backtrack step** — undo the last choice and try the next one.
## The template
```python
def solve(data):
result = []
path = []
def backtrack(index):
# 1. Base case
if is_complete(index, path):
result.append(path[:]) # copy — lists are passed by reference
return
# 2. Choices
for choice in get_choices(index, data):
# 3. Constraints
if not is_valid(choice, path):
continue
path.append(choice) # make the choice
backtrack(index + 1) # explore
path.pop() # 4. Backtrack
backtrack(0)
return result
```
## Steps to apply the template
1. Draw the decision tree for a small input. Name each level and each branch.
2. Write the base case. Ask: when is the path complete?
3. Write the choices. Ask: what can I add at this level?
4. Write the constraints. Ask: what makes a path invalid? Reject it before recursion.
5. Add the backtrack step. Pop what you appended.
6. Copy the path when you save it. Never append the same list reference.
## Four problems, one template
### Subsets
Choice: include or skip each number. No constraint. Base case: index equals list length.
```python
def subsets(nums):
result, path = [], []
def backtrack(i):
if i == len(nums):
result.append(path[:])
return
path.append(nums[i]) # include
backtrack(i + 1)
path.pop()
backtrack(i + 1) # skip
backtrack(0)
return result
```
### Permutations
Same choices as subsets. Constraint: do not reuse a number already in the path. Base case: path length equals list length.
```python
def permute(nums):
result, path = [], []
used = [False] * len(nums)
def backtrack():
if len(path) == len(nums):
result.append(path[:])
return
for i in range(len(nums)):
if used[i]:
continue
used[i] = True
path.append(nums[i])
backtrack()
path.pop()
used[i] = False
backtrack()
return result
```
### Combinations
Same as subsets. Change one line: save the path only when its length equals `k`.
```python
def combine(n, k):
result, path = [], []
def backtrack(start):
if len(path) == k:
result.append(path[:])
return
for i in range(start, n + 1):
path.append(i)
backtrack(i + 1)
path.pop()
backtrack(1)
return result
```
### N-Queens
Choice: which column to place the queen in the current row. Constraint: no shared column or diagonal. Base case: `n` queens placed. Backtrack: remove the last queen.
```python
def solve_n_queens(n):
result, cols = [], []
used_cols, diag1, diag2 = set(), set(), set()
def backtrack(row):
if row == n:
result.append(cols[:])
return
for col in range(n):
if col in used_cols or row - col in diag1 or row + col in diag2:
continue
cols.append(col)
used_cols.add(col); diag1.add(row - col); diag2.add(row + col)
backtrack(row + 1)
cols.pop()
used_cols.remove(col); diag1.remove(row - col); diag2.remove(row + col)
backtrack(0)
return result
```
## Common mistakes
- You append the `path` list itself instead of a copy. The result fills with empty lists.
- You forget to pop. The path grows and never resets.
- You check constraints after recursion instead of before. You waste time on dead paths.
- You use backtracking when you need only one answer. Use greedy or DP instead.