feat(patterns): strategy tutorials
This commit is contained in:
331
backend/data/patterns/backtracking.yaml
Normal file
331
backend/data/patterns/backtracking.yaml
Normal file
@@ -0,0 +1,331 @@
|
|||||||
|
name: Backtracking
|
||||||
|
slug: backtracking
|
||||||
|
difficulty_level: 3
|
||||||
|
|
||||||
|
description: >
|
||||||
|
Build solutions incrementally, exploring choices one at a time and abandoning
|
||||||
|
paths that fail to satisfy constraints. Backtracking systematically explores
|
||||||
|
all possibilities by making a choice, recursing, then undoing the choice.
|
||||||
|
|
||||||
|
when_to_use: |
|
||||||
|
- Generating all permutations or combinations
|
||||||
|
- Constraint satisfaction (Sudoku, N-Queens)
|
||||||
|
- Subset/partition problems
|
||||||
|
- Path finding with constraints
|
||||||
|
- Word search in grids
|
||||||
|
|
||||||
|
metaphor: |
|
||||||
|
Imagine navigating a maze by always trying the left path first. When you hit
|
||||||
|
a dead end, you backtrack to the last intersection and try the next option.
|
||||||
|
You systematically explore every possible route, backing up whenever you reach
|
||||||
|
an invalid state.
|
||||||
|
|
||||||
|
Another analogy: filling out a Sudoku puzzle. You try a number in an empty cell.
|
||||||
|
If it leads to a contradiction later, you erase it (backtrack) and try the next
|
||||||
|
number. If no number works, you backtrack further to a previous cell.
|
||||||
|
|
||||||
|
core_concept: |
|
||||||
|
Backtracking is a refined brute force that prunes invalid branches early.
|
||||||
|
The pattern follows a template:
|
||||||
|
|
||||||
|
1. **Choose**: Make a choice (add element to path, place a queen, etc.)
|
||||||
|
2. **Explore**: Recurse to explore consequences of that choice
|
||||||
|
3. **Unchoose**: Undo the choice (backtrack) to try alternatives
|
||||||
|
|
||||||
|
The key insight is that we build a **decision tree** where each node represents
|
||||||
|
a state and edges represent choices. Backtracking is a DFS of this tree, with
|
||||||
|
pruning of subtrees that can't lead to valid solutions.
|
||||||
|
|
||||||
|
**Pruning** is what makes backtracking efficient. By detecting invalid states
|
||||||
|
early, we avoid exploring exponentially many useless branches.
|
||||||
|
|
||||||
|
visualization: |
|
||||||
|
**Generating subsets of [1, 2, 3]:**
|
||||||
|
|
||||||
|
```
|
||||||
|
Decision tree (include or exclude each element):
|
||||||
|
|
||||||
|
[]
|
||||||
|
/ \
|
||||||
|
[1] []
|
||||||
|
/ \ / \
|
||||||
|
[1,2] [1] [2] []
|
||||||
|
/ \ / \ / \ / \
|
||||||
|
[1,2,3][1,2][1,3][1][2,3][2][3][]
|
||||||
|
|
||||||
|
Subsets: [], [1], [2], [3], [1,2], [1,3], [2,3], [1,2,3]
|
||||||
|
```
|
||||||
|
|
||||||
|
**N-Queens (N=4) with pruning:**
|
||||||
|
|
||||||
|
```
|
||||||
|
Place queens row by row, checking column and diagonal conflicts:
|
||||||
|
|
||||||
|
Row 0: Try col 0
|
||||||
|
Row 1: col 0 ❌ (same col)
|
||||||
|
col 1 ❌ (diagonal)
|
||||||
|
col 2 ✓
|
||||||
|
Row 2: col 0 ❌ (diagonal)
|
||||||
|
col 1 ❌ (col 1 attacks via diagonal)
|
||||||
|
col 2 ❌ (same col)
|
||||||
|
col 3 ❌ (diagonal)
|
||||||
|
Backtrack!
|
||||||
|
Row 1: col 3 ✓
|
||||||
|
Row 2: col 1 ✓
|
||||||
|
Row 3: col 0 ❌
|
||||||
|
col 1 ❌
|
||||||
|
col 2 ❌
|
||||||
|
col 3 ❌
|
||||||
|
Backtrack!
|
||||||
|
...
|
||||||
|
|
||||||
|
Eventually find: [1, 3, 0, 2] (queens at cols 1,3,0,2 for rows 0,1,2,3)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Template visualization:**
|
||||||
|
|
||||||
|
```
|
||||||
|
backtrack(state):
|
||||||
|
if is_solution(state):
|
||||||
|
record_solution(state)
|
||||||
|
return
|
||||||
|
|
||||||
|
for choice in get_choices(state):
|
||||||
|
if is_valid(choice, state): ← PRUNE
|
||||||
|
make_choice(choice, state) ← CHOOSE
|
||||||
|
backtrack(state) ← EXPLORE
|
||||||
|
undo_choice(choice, state) ← UNCHOOSE
|
||||||
|
```
|
||||||
|
|
||||||
|
code_template: |
|
||||||
|
def generate_subsets(nums: list[int]) -> list[list[int]]:
|
||||||
|
"""Generate all subsets (power set)."""
|
||||||
|
result = []
|
||||||
|
|
||||||
|
def backtrack(start: int, path: list[int]):
|
||||||
|
result.append(path[:]) # Record current 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
|
||||||
|
|
||||||
|
|
||||||
|
def generate_permutations(nums: list[int]) -> list[list[int]]:
|
||||||
|
"""Generate all permutations."""
|
||||||
|
result = []
|
||||||
|
used = [False] * len(nums)
|
||||||
|
|
||||||
|
def backtrack(path: list[int]):
|
||||||
|
if len(path) == len(nums):
|
||||||
|
result.append(path[:])
|
||||||
|
return
|
||||||
|
|
||||||
|
for i in range(len(nums)):
|
||||||
|
if used[i]:
|
||||||
|
continue
|
||||||
|
|
||||||
|
used[i] = True # Choose
|
||||||
|
path.append(nums[i])
|
||||||
|
backtrack(path) # Explore
|
||||||
|
path.pop() # Unchoose
|
||||||
|
used[i] = False
|
||||||
|
|
||||||
|
backtrack([])
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def combination_sum(candidates: list[int], target: int) -> list[list[int]]:
|
||||||
|
"""Find combinations that sum to target (can reuse elements)."""
|
||||||
|
result = []
|
||||||
|
|
||||||
|
def backtrack(start: int, path: list[int], remaining: int):
|
||||||
|
if remaining == 0:
|
||||||
|
result.append(path[:])
|
||||||
|
return
|
||||||
|
|
||||||
|
for i in range(start, len(candidates)):
|
||||||
|
if candidates[i] > remaining: # Prune
|
||||||
|
continue
|
||||||
|
|
||||||
|
path.append(candidates[i])
|
||||||
|
backtrack(i, path, remaining - candidates[i]) # Can reuse
|
||||||
|
path.pop()
|
||||||
|
|
||||||
|
candidates.sort() # Enable pruning
|
||||||
|
backtrack(0, [], target)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def solve_n_queens(n: int) -> list[list[str]]:
|
||||||
|
"""Find all valid N-Queens solutions."""
|
||||||
|
result = []
|
||||||
|
cols = set()
|
||||||
|
diag1 = set() # row - col
|
||||||
|
diag2 = set() # row + col
|
||||||
|
|
||||||
|
def backtrack(row: int, queens: list[int]):
|
||||||
|
if row == n:
|
||||||
|
# Convert to board representation
|
||||||
|
board = []
|
||||||
|
for c in queens:
|
||||||
|
board.append('.' * c + 'Q' + '.' * (n - c - 1))
|
||||||
|
result.append(board)
|
||||||
|
return
|
||||||
|
|
||||||
|
for col in range(n):
|
||||||
|
if col in cols or (row - col) in diag1 or (row + col) in diag2:
|
||||||
|
continue # Prune: under attack
|
||||||
|
|
||||||
|
# Choose
|
||||||
|
cols.add(col)
|
||||||
|
diag1.add(row - col)
|
||||||
|
diag2.add(row + col)
|
||||||
|
queens.append(col)
|
||||||
|
|
||||||
|
backtrack(row + 1, queens) # Explore
|
||||||
|
|
||||||
|
# Unchoose
|
||||||
|
queens.pop()
|
||||||
|
cols.remove(col)
|
||||||
|
diag1.remove(row - col)
|
||||||
|
diag2.remove(row + col)
|
||||||
|
|
||||||
|
backtrack(0, [])
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def word_search(board: list[list[str]], word: str) -> bool:
|
||||||
|
"""Check if word exists in grid following adjacent cells."""
|
||||||
|
rows, cols = len(board), len(board[0])
|
||||||
|
|
||||||
|
def backtrack(r: int, c: int, i: int) -> bool:
|
||||||
|
if i == len(word):
|
||||||
|
return True
|
||||||
|
|
||||||
|
if (r < 0 or r >= rows or c < 0 or c >= cols
|
||||||
|
or board[r][c] != word[i]):
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Choose: mark as visited
|
||||||
|
temp = board[r][c]
|
||||||
|
board[r][c] = '#'
|
||||||
|
|
||||||
|
# Explore neighbors
|
||||||
|
found = (backtrack(r + 1, c, i + 1) or
|
||||||
|
backtrack(r - 1, c, i + 1) or
|
||||||
|
backtrack(r, c + 1, i + 1) or
|
||||||
|
backtrack(r, c - 1, i + 1))
|
||||||
|
|
||||||
|
# Unchoose: restore
|
||||||
|
board[r][c] = temp
|
||||||
|
|
||||||
|
return found
|
||||||
|
|
||||||
|
for r in range(rows):
|
||||||
|
for c in range(cols):
|
||||||
|
if backtrack(r, c, 0):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
recognition_signals:
|
||||||
|
- "all permutations"
|
||||||
|
- "all combinations"
|
||||||
|
- "all subsets"
|
||||||
|
- "generate all"
|
||||||
|
- "N-Queens"
|
||||||
|
- "Sudoku"
|
||||||
|
- "word search"
|
||||||
|
- "partition"
|
||||||
|
- "constraint satisfaction"
|
||||||
|
- "valid arrangements"
|
||||||
|
|
||||||
|
common_mistakes:
|
||||||
|
- title: Forgetting to unchoose (backtrack)
|
||||||
|
description: |
|
||||||
|
Not undoing the choice after exploring leaves state modified, causing
|
||||||
|
incorrect results for subsequent branches.
|
||||||
|
fix: |
|
||||||
|
Always pair choose with unchoose:
|
||||||
|
```python
|
||||||
|
path.append(choice) # Choose
|
||||||
|
backtrack(...) # Explore
|
||||||
|
path.pop() # Unchoose - MUST DO!
|
||||||
|
```
|
||||||
|
|
||||||
|
- title: Modifying state without copying
|
||||||
|
description: |
|
||||||
|
Adding the same path object to results multiple times—they all reference
|
||||||
|
the same list that keeps changing.
|
||||||
|
fix: |
|
||||||
|
Copy the path when recording:
|
||||||
|
```python
|
||||||
|
result.append(path[:]) # Shallow copy
|
||||||
|
# or
|
||||||
|
result.append(list(path))
|
||||||
|
```
|
||||||
|
|
||||||
|
- title: Not pruning effectively
|
||||||
|
description: |
|
||||||
|
Checking validity only at leaf nodes means exploring many invalid
|
||||||
|
branches that could have been cut early.
|
||||||
|
fix: |
|
||||||
|
Validate as early as possible:
|
||||||
|
```python
|
||||||
|
for choice in choices:
|
||||||
|
if is_valid(choice): # Prune BEFORE recursing
|
||||||
|
make_choice(choice)
|
||||||
|
backtrack(...)
|
||||||
|
```
|
||||||
|
|
||||||
|
- title: Wrong base case
|
||||||
|
description: |
|
||||||
|
Recording partial solutions as complete, or not recognizing when a
|
||||||
|
complete solution is reached.
|
||||||
|
fix: |
|
||||||
|
Clearly define what constitutes a complete solution:
|
||||||
|
- Permutations: path length equals input length
|
||||||
|
- Subsets: (all indices are complete solutions)
|
||||||
|
- N-Queens: placed N queens
|
||||||
|
|
||||||
|
variations:
|
||||||
|
- name: Subsets
|
||||||
|
description: |
|
||||||
|
Generate all subsets. Each element is either included or not. Record
|
||||||
|
at every node, not just leaves.
|
||||||
|
example: "Subsets, Subsets II (with duplicates)"
|
||||||
|
|
||||||
|
- name: Permutations
|
||||||
|
description: |
|
||||||
|
Generate all orderings. Track which elements are used. Record only at
|
||||||
|
leaves (when all elements used).
|
||||||
|
example: "Permutations, Permutations II"
|
||||||
|
|
||||||
|
- name: Combinations
|
||||||
|
description: |
|
||||||
|
Generate subsets of specific size k, or combinations that meet a target
|
||||||
|
sum. Use start index to avoid duplicates.
|
||||||
|
example: "Combinations, Combination Sum"
|
||||||
|
|
||||||
|
- name: Constraint satisfaction
|
||||||
|
description: |
|
||||||
|
Place elements satisfying constraints (non-attacking queens, valid
|
||||||
|
Sudoku). Heavy pruning based on constraints.
|
||||||
|
example: "N-Queens, Sudoku Solver"
|
||||||
|
|
||||||
|
- name: Path finding
|
||||||
|
description: |
|
||||||
|
Find paths in grids or graphs, marking visited cells temporarily.
|
||||||
|
Unmark when backtracking.
|
||||||
|
example: "Word Search, Unique Paths III"
|
||||||
|
|
||||||
|
related_patterns:
|
||||||
|
- dfs
|
||||||
|
- dynamic-programming
|
||||||
|
|
||||||
|
prerequisite_patterns:
|
||||||
|
- dfs
|
||||||
299
backend/data/patterns/greedy.yaml
Normal file
299
backend/data/patterns/greedy.yaml
Normal file
@@ -0,0 +1,299 @@
|
|||||||
|
name: Greedy
|
||||||
|
slug: greedy
|
||||||
|
difficulty_level: 3
|
||||||
|
|
||||||
|
description: >
|
||||||
|
Make locally optimal choices at each step, hoping to find a global optimum.
|
||||||
|
Greedy algorithms are simple and efficient but only work when the problem
|
||||||
|
has the greedy choice property—local optima lead to global optimum.
|
||||||
|
|
||||||
|
when_to_use: |
|
||||||
|
- Interval scheduling (activity selection)
|
||||||
|
- Huffman coding
|
||||||
|
- Minimum spanning tree (Prim's, Kruskal's)
|
||||||
|
- Shortest path with non-negative weights (Dijkstra's)
|
||||||
|
- Fractional knapsack
|
||||||
|
|
||||||
|
metaphor: |
|
||||||
|
Imagine eating at a buffet where you can only fill your plate once. The greedy
|
||||||
|
strategy: always take the food that looks most appealing right now. This works
|
||||||
|
if what looks best now is actually best overall—but fails if you fill up on
|
||||||
|
appetizers and miss the main course.
|
||||||
|
|
||||||
|
Another analogy: making change with the fewest coins. For US currency, always
|
||||||
|
using the largest coin that fits (quarter before dime before nickel) gives
|
||||||
|
optimal results. But with coins [1, 3, 4], making 6 cents: greedy gives
|
||||||
|
4+1+1=3 coins, while optimal is 3+3=2 coins.
|
||||||
|
|
||||||
|
core_concept: |
|
||||||
|
Greedy algorithms work by making the choice that seems best **at each step**
|
||||||
|
without reconsidering previous choices. This works when:
|
||||||
|
|
||||||
|
1. **Greedy choice property**: A locally optimal choice is part of some
|
||||||
|
globally optimal solution.
|
||||||
|
|
||||||
|
2. **Optimal substructure**: After making a greedy choice, the remaining
|
||||||
|
subproblem has the same structure as the original.
|
||||||
|
|
||||||
|
The key insight is recognizing when greedy works. Common patterns:
|
||||||
|
- **Sort by deadline/end time** for scheduling problems
|
||||||
|
- **Sort by ratio** (value/weight) for selection problems
|
||||||
|
- **Always pick the nearest/smallest/largest** when monotonicity guarantees optimality
|
||||||
|
|
||||||
|
When greedy doesn't work (like 0/1 Knapsack), use dynamic programming instead.
|
||||||
|
|
||||||
|
visualization: |
|
||||||
|
**Activity Selection (maximize non-overlapping activities):**
|
||||||
|
|
||||||
|
```
|
||||||
|
Activities: [(1,4), (3,5), (0,6), (5,7), (3,9), (5,9), (6,10), (8,11)]
|
||||||
|
Sort by end time: [(1,4), (3,5), (0,6), (5,7), (3,9), (5,9), (6,10), (8,11)]
|
||||||
|
|
||||||
|
Greedy selection:
|
||||||
|
- (1,4): Select ✓ (first activity)
|
||||||
|
- (3,5): Skip ✗ (overlaps with (1,4))
|
||||||
|
- (0,6): Skip ✗ (overlaps)
|
||||||
|
- (5,7): Select ✓ (starts after 4)
|
||||||
|
- (3,9): Skip ✗ (overlaps)
|
||||||
|
- (5,9): Skip ✗ (overlaps)
|
||||||
|
- (6,10): Skip ✗ (overlaps with (5,7))
|
||||||
|
- (8,11): Select ✓ (starts after 7)
|
||||||
|
|
||||||
|
Selected: [(1,4), (5,7), (8,11)] — 3 activities
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why sort by end time?**
|
||||||
|
|
||||||
|
```
|
||||||
|
Intuition: Finishing early leaves maximum room for future activities.
|
||||||
|
|
||||||
|
If we picked an activity ending later but overlapping with one ending earlier:
|
||||||
|
- We'd block the same activities (both overlap with them)
|
||||||
|
- But we'd also potentially block more future activities
|
||||||
|
- So the earlier-ending activity is never worse
|
||||||
|
```
|
||||||
|
|
||||||
|
**Jump Game (can reach end?):**
|
||||||
|
|
||||||
|
```
|
||||||
|
Array: [2, 3, 1, 1, 4]
|
||||||
|
|
||||||
|
Greedy: Track farthest reachable position
|
||||||
|
|
||||||
|
i=0: farthest = max(0, 0+2) = 2
|
||||||
|
i=1: farthest = max(2, 1+3) = 4 ← can reach end!
|
||||||
|
i=2: farthest = max(4, 2+1) = 4
|
||||||
|
i=3: farthest = max(4, 3+1) = 4
|
||||||
|
i=4: reached end ✓
|
||||||
|
```
|
||||||
|
|
||||||
|
code_template: |
|
||||||
|
def activity_selection(activities: list[tuple[int, int]]) -> list[tuple]:
|
||||||
|
"""Select maximum non-overlapping activities."""
|
||||||
|
# Sort by end time
|
||||||
|
activities.sort(key=lambda x: x[1])
|
||||||
|
|
||||||
|
result = [activities[0]]
|
||||||
|
last_end = activities[0][1]
|
||||||
|
|
||||||
|
for start, end in activities[1:]:
|
||||||
|
if start >= last_end: # No overlap
|
||||||
|
result.append((start, end))
|
||||||
|
last_end = end
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def can_jump(nums: list[int]) -> bool:
|
||||||
|
"""Check if you can reach the last index."""
|
||||||
|
farthest = 0
|
||||||
|
|
||||||
|
for i in range(len(nums)):
|
||||||
|
if i > farthest:
|
||||||
|
return False # Can't reach this position
|
||||||
|
farthest = max(farthest, i + nums[i])
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def min_jumps(nums: list[int]) -> int:
|
||||||
|
"""Minimum jumps to reach the last index."""
|
||||||
|
if len(nums) <= 1:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
jumps = 0
|
||||||
|
current_end = 0
|
||||||
|
farthest = 0
|
||||||
|
|
||||||
|
for i in range(len(nums) - 1):
|
||||||
|
farthest = max(farthest, i + nums[i])
|
||||||
|
|
||||||
|
if i == current_end:
|
||||||
|
jumps += 1
|
||||||
|
current_end = farthest
|
||||||
|
|
||||||
|
return jumps
|
||||||
|
|
||||||
|
|
||||||
|
def fractional_knapsack(capacity: int,
|
||||||
|
items: list[tuple[int, int]]) -> float:
|
||||||
|
"""Maximum value with fractional items. Items are (value, weight)."""
|
||||||
|
# Sort by value-to-weight ratio (descending)
|
||||||
|
items.sort(key=lambda x: x[0] / x[1], reverse=True)
|
||||||
|
|
||||||
|
total_value = 0
|
||||||
|
|
||||||
|
for value, weight in items:
|
||||||
|
if capacity >= weight:
|
||||||
|
total_value += value
|
||||||
|
capacity -= weight
|
||||||
|
else:
|
||||||
|
# Take fraction of this item
|
||||||
|
total_value += value * (capacity / weight)
|
||||||
|
break
|
||||||
|
|
||||||
|
return total_value
|
||||||
|
|
||||||
|
|
||||||
|
def min_meeting_rooms(intervals: list[list[int]]) -> int:
|
||||||
|
"""Minimum meeting rooms needed."""
|
||||||
|
events = []
|
||||||
|
for start, end in intervals:
|
||||||
|
events.append((start, 1)) # Start event
|
||||||
|
events.append((end, -1)) # End event
|
||||||
|
|
||||||
|
events.sort()
|
||||||
|
|
||||||
|
rooms = 0
|
||||||
|
max_rooms = 0
|
||||||
|
|
||||||
|
for _, delta in events:
|
||||||
|
rooms += delta
|
||||||
|
max_rooms = max(max_rooms, rooms)
|
||||||
|
|
||||||
|
return max_rooms
|
||||||
|
|
||||||
|
|
||||||
|
def partition_labels(s: str) -> list[int]:
|
||||||
|
"""Partition string so each letter appears in at most one part."""
|
||||||
|
last = {c: i for i, c in enumerate(s)}
|
||||||
|
|
||||||
|
partitions = []
|
||||||
|
start = end = 0
|
||||||
|
|
||||||
|
for i, c in enumerate(s):
|
||||||
|
end = max(end, last[c]) # Extend partition to include all of char c
|
||||||
|
|
||||||
|
if i == end: # Reached end of partition
|
||||||
|
partitions.append(end - start + 1)
|
||||||
|
start = i + 1
|
||||||
|
|
||||||
|
return partitions
|
||||||
|
|
||||||
|
|
||||||
|
def gas_station(gas: list[int], cost: list[int]) -> int:
|
||||||
|
"""Find starting station to complete circuit."""
|
||||||
|
total_surplus = 0
|
||||||
|
current_surplus = 0
|
||||||
|
start = 0
|
||||||
|
|
||||||
|
for i in range(len(gas)):
|
||||||
|
total_surplus += gas[i] - cost[i]
|
||||||
|
current_surplus += gas[i] - cost[i]
|
||||||
|
|
||||||
|
if current_surplus < 0:
|
||||||
|
# Can't reach next station from current start
|
||||||
|
start = i + 1
|
||||||
|
current_surplus = 0
|
||||||
|
|
||||||
|
return start if total_surplus >= 0 else -1
|
||||||
|
|
||||||
|
recognition_signals:
|
||||||
|
- "maximum/minimum number"
|
||||||
|
- "interval scheduling"
|
||||||
|
- "activity selection"
|
||||||
|
- "jump game"
|
||||||
|
- "gas station"
|
||||||
|
- "partition"
|
||||||
|
- "assign"
|
||||||
|
- "optimal"
|
||||||
|
- "greedy"
|
||||||
|
- "earliest/latest"
|
||||||
|
- "most/least"
|
||||||
|
|
||||||
|
common_mistakes:
|
||||||
|
- title: Applying greedy when it doesn't work
|
||||||
|
description: |
|
||||||
|
Not all optimization problems have the greedy choice property. Using
|
||||||
|
greedy on 0/1 Knapsack or Coin Change (with arbitrary coins) gives
|
||||||
|
suboptimal results.
|
||||||
|
fix: |
|
||||||
|
Verify greedy works by proving the greedy choice property, or test
|
||||||
|
against known cases. When in doubt, use dynamic programming.
|
||||||
|
|
||||||
|
- title: Wrong sorting criteria
|
||||||
|
description: |
|
||||||
|
Sorting by the wrong attribute (e.g., start time instead of end time
|
||||||
|
for activity selection) leads to suboptimal selections.
|
||||||
|
fix: |
|
||||||
|
Think about what greedy property you're exploiting. For "maximize
|
||||||
|
activities," ending early maximizes remaining time. For "minimize
|
||||||
|
lateness," sorting by deadline helps.
|
||||||
|
|
||||||
|
- title: Not handling edge cases
|
||||||
|
description: |
|
||||||
|
Empty input, single element, or already-solved cases often need
|
||||||
|
special handling.
|
||||||
|
fix: |
|
||||||
|
Check for edge cases before main algorithm:
|
||||||
|
```python
|
||||||
|
if not items:
|
||||||
|
return 0
|
||||||
|
if len(items) == 1:
|
||||||
|
return items[0]
|
||||||
|
```
|
||||||
|
|
||||||
|
- title: Greedy from wrong direction
|
||||||
|
description: |
|
||||||
|
Sometimes greedy works forward but not backward (or vice versa).
|
||||||
|
Processing in the wrong order gives wrong results.
|
||||||
|
fix: |
|
||||||
|
Consider both directions. For interval problems, usually sort by end
|
||||||
|
time and process forward. For some problems, working backward reveals
|
||||||
|
the greedy choice more clearly.
|
||||||
|
|
||||||
|
variations:
|
||||||
|
- name: Activity/interval selection
|
||||||
|
description: |
|
||||||
|
Select maximum non-overlapping intervals. Sort by end time, greedily
|
||||||
|
select if no overlap with previous.
|
||||||
|
example: "Activity Selection, Non-overlapping Intervals"
|
||||||
|
|
||||||
|
- name: Jump/reach problems
|
||||||
|
description: |
|
||||||
|
Track farthest reachable position, greedily extend reach.
|
||||||
|
example: "Jump Game, Jump Game II, Video Stitching"
|
||||||
|
|
||||||
|
- name: Assignment problems
|
||||||
|
description: |
|
||||||
|
Match items greedily based on some criteria (smallest to smallest,
|
||||||
|
largest to largest, etc.).
|
||||||
|
example: "Assign Cookies, Boats to Save People"
|
||||||
|
|
||||||
|
- name: Scheduling
|
||||||
|
description: |
|
||||||
|
Schedule tasks to minimize lateness or maximize throughput. Often
|
||||||
|
involves sorting by deadline or duration.
|
||||||
|
example: "Task Scheduler, Meeting Rooms, Car Pooling"
|
||||||
|
|
||||||
|
- name: Huffman coding
|
||||||
|
description: |
|
||||||
|
Greedily merge two lowest-frequency nodes to build optimal prefix-free
|
||||||
|
encoding tree.
|
||||||
|
example: "Huffman Coding (not on LeetCode, but classic)"
|
||||||
|
|
||||||
|
related_patterns:
|
||||||
|
- dynamic-programming
|
||||||
|
- intervals
|
||||||
|
|
||||||
|
prerequisite_patterns: []
|
||||||
Reference in New Issue
Block a user