mirror of
https://github.com/prdlk/leetcode.git
synced 2026-11-03 05:27:36 +00:00
feat: add lc 46, 39, 78
This commit is contained in:
1 parent
ac4dc654fc
commit
f0f479d6cf
8 files changed
+496
No files matched your search
@@ -0,0 +1,163 @@
|
||||
---
|
||||
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.
|
||||
|
||||
|
||||
Reference in new issue
Block a user