feat(content): test cases batch 2
This commit is contained in:
@@ -11,108 +11,160 @@ patterns:
|
||||
- dfs
|
||||
|
||||
description: |
|
||||
Given an m x n grid of characters `board` and a string `word`, return true if `word` exists
|
||||
in the grid.
|
||||
Given an `m × n` grid of characters `board` and a string `word`, return `true` if `word` exists in the grid.
|
||||
|
||||
The word can be constructed from letters of sequentially adjacent cells, where adjacent cells
|
||||
are horizontally or vertically neighboring. The same letter cell may not be used more than once.
|
||||
The word can be constructed from letters of sequentially **adjacent** cells, where adjacent cells are horizontally or vertically neighboring. The same letter cell may **not be used more than once**.
|
||||
|
||||
constraints: |
|
||||
- m == board.length
|
||||
- n == board[i].length
|
||||
- 1 <= m, n <= 6
|
||||
- 1 <= word.length <= 15
|
||||
- board and word consist of only lowercase and uppercase English letters
|
||||
- `m == board.length`
|
||||
- `n == board[i].length`
|
||||
- `1 <= m, n <= 6`
|
||||
- `1 <= word.length <= 15`
|
||||
- `board` and `word` consist of only lowercase and uppercase English letters
|
||||
|
||||
examples:
|
||||
- input: 'board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED"'
|
||||
output: "true"
|
||||
explanation: "Path exists starting from top-left corner."
|
||||
explanation: "Path: A(0,0) → B(0,1) → C(0,2) → C(1,2) → E(2,2) → D(2,1)"
|
||||
- input: 'board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "SEE"'
|
||||
output: "true"
|
||||
explanation: "Path exists."
|
||||
explanation: "Path: S(1,3) → E(2,3) → E(2,2)"
|
||||
- input: 'board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCB"'
|
||||
output: "false"
|
||||
explanation: "Would need to reuse 'B' cell."
|
||||
explanation: "Would require reusing the 'B' cell at (0,1)."
|
||||
|
||||
explanation:
|
||||
approach: |
|
||||
1. For each cell, try to start the word from there
|
||||
2. Use DFS with backtracking to explore all paths
|
||||
3. Mark cells as visited during exploration
|
||||
4. Unmark cells when backtracking (restore state)
|
||||
5. If entire word is matched, return true
|
||||
|
||||
intuition: |
|
||||
This is a classic backtracking problem. We explore paths character by character,
|
||||
and if we reach a dead end (no valid next character), we backtrack and try a
|
||||
different direction.
|
||||
Imagine walking through a maze of letters, trying to spell out a word. At each step, you can move up, down, left, or right to an adjacent cell. But there's a rule: you can't step on the same cell twice.
|
||||
|
||||
The key is marking cells as visited during exploration to avoid reusing them,
|
||||
then unmarking when we backtrack to allow other paths to use them.
|
||||
This is a classic **backtracking** problem. We try a path, and if it leads to a dead end (wrong character or no valid moves), we **backtrack** — undo our steps and try a different direction.
|
||||
|
||||
Think of it like this:
|
||||
1. Start from any cell that matches the first character
|
||||
2. From there, try to find the second character in any adjacent cell
|
||||
3. Mark cells as "visited" to prevent reuse
|
||||
4. If we hit a dead end, **unmark** the cell and try another path
|
||||
5. If we match all characters, we've found the word!
|
||||
|
||||
The key insight is that backtracking requires **restoring state** after each failed attempt.
|
||||
|
||||
approach: |
|
||||
We solve this using **DFS with Backtracking**:
|
||||
|
||||
**Step 1: Try every cell as a starting point**
|
||||
|
||||
- Iterate through all cells in the grid
|
||||
- For each cell, attempt to find the word starting there
|
||||
|
||||
|
||||
|
||||
**Step 2: Define the DFS function**
|
||||
|
||||
- `dfs(row, col, index)` returns True if we can find `word[index:]` starting from `(row, col)`
|
||||
- Base case: if `index == len(word)`, we've matched everything — return True
|
||||
- Boundary check: if out of bounds, return False
|
||||
- Character check: if `board[row][col] != word[index]`, return False
|
||||
|
||||
|
||||
|
||||
**Step 3: Mark, explore, and unmark**
|
||||
|
||||
- **Mark**: Temporarily change `board[row][col]` to `'#'` to prevent reuse
|
||||
- **Explore**: Recursively check all four directions with `index + 1`
|
||||
- **Unmark**: Restore `board[row][col]` to its original value (backtrack)
|
||||
|
||||
|
||||
|
||||
**Step 4: Return result**
|
||||
|
||||
- If any DFS call returns True, the word exists
|
||||
- If all starting points fail, return False
|
||||
|
||||
|
||||
|
||||
The unmarking step is crucial — it allows other paths to use the same cell.
|
||||
|
||||
common_pitfalls:
|
||||
- title: Not restoring visited state
|
||||
- title: Not Restoring Visited State
|
||||
description: |
|
||||
After exploring a path, you must unmark the cell as visited.
|
||||
Otherwise, other paths from earlier cells can't use it.
|
||||
wrong_approach: "Only marking, never unmarking"
|
||||
correct_approach: "Mark before recursion, unmark after"
|
||||
After exploring a path, you **must** restore the cell's original value. Otherwise, other paths can't use that cell.
|
||||
|
||||
- title: Modifying board permanently
|
||||
description: |
|
||||
If you change board[r][c] to mark as visited, restore it after backtracking.
|
||||
```python
|
||||
# WRONG: Cell stays marked forever
|
||||
board[r][c] = '#'
|
||||
result = dfs(...)
|
||||
return result
|
||||
|
||||
- title: Checking word completion too late
|
||||
# RIGHT: Restore after exploring
|
||||
board[r][c] = '#'
|
||||
result = dfs(...)
|
||||
board[r][c] = original_value # Backtrack!
|
||||
return result
|
||||
```
|
||||
wrong_approach: "Only marking cells, never restoring"
|
||||
correct_approach: "Store original value, mark, explore, restore"
|
||||
|
||||
- title: Checking Word Completion Too Late
|
||||
description: |
|
||||
Check if entire word is matched (index == len(word)) at the start of DFS,
|
||||
before any bounds/character checks.
|
||||
Check if `index == len(word)` **before** bounds and character checks. Otherwise, when we've matched all characters, we might return False due to being "out of bounds" at the next position.
|
||||
wrong_approach: "Checking bounds/character before word completion"
|
||||
correct_approach: "if index == len(word): return True # Check first!"
|
||||
|
||||
- title: Not Trying All Directions
|
||||
description: |
|
||||
You must explore all four directions: up, down, left, right. Missing any direction means missing potential valid paths.
|
||||
|
||||
Use short-circuit OR: `dfs(r+1,c) or dfs(r-1,c) or dfs(r,c+1) or dfs(r,c-1)`
|
||||
wrong_approach: "Only checking some directions"
|
||||
correct_approach: "Explore all four orthogonal directions"
|
||||
|
||||
key_takeaways:
|
||||
- Backtracking = DFS with state restoration
|
||||
- Mark and unmark visited cells around recursive calls
|
||||
- Early termination when full word is found
|
||||
- Grid constraints allow brute force (small board size)
|
||||
- "**Backtracking = DFS + state restoration**: Mark before recursion, unmark after"
|
||||
- "**Early termination**: Return True as soon as the word is found"
|
||||
- "**In-place marking**: Using `'#'` to mark cells avoids extra space for a visited set"
|
||||
- "**Small constraints enable brute force**: With m, n ≤ 6 and word ≤ 15, exponential exploration is acceptable"
|
||||
|
||||
time_complexity: "O(m × n × 3^L)"
|
||||
space_complexity: "O(L)"
|
||||
complexity_explanation: |
|
||||
Time: Start from each cell, explore up to 3 directions (not the one we came from) for L characters.
|
||||
Space: Recursion depth is at most word length L.
|
||||
time_complexity: "O(m × n × 3^L). We try each cell as a start, and from each cell, we explore up to 3 directions (excluding where we came from) for L characters."
|
||||
space_complexity: "O(L). The recursion stack depth equals the word length L."
|
||||
|
||||
solutions:
|
||||
- approach_name: DFS with Backtracking (Optimal)
|
||||
- approach_name: DFS with Backtracking
|
||||
is_optimal: true
|
||||
code: |
|
||||
def exist(board: list[list[str]], word: str) -> bool:
|
||||
rows, cols = len(board), len(board[0])
|
||||
|
||||
def dfs(r: int, c: int, i: int) -> bool:
|
||||
# Base case: found all characters
|
||||
if i == len(word):
|
||||
return True
|
||||
|
||||
# Boundary check
|
||||
if r < 0 or r >= rows or c < 0 or c >= cols:
|
||||
return False
|
||||
|
||||
# Character mismatch
|
||||
if board[r][c] != word[i]:
|
||||
return False
|
||||
|
||||
# Mark as visited
|
||||
temp = board[r][c]
|
||||
# Mark cell as visited (temporarily)
|
||||
original = board[r][c]
|
||||
board[r][c] = '#'
|
||||
|
||||
# Explore all 4 directions
|
||||
# Explore all four directions
|
||||
found = (
|
||||
dfs(r + 1, c, i + 1) or
|
||||
dfs(r - 1, c, i + 1) or
|
||||
dfs(r, c + 1, i + 1) or
|
||||
dfs(r, c - 1, i + 1)
|
||||
dfs(r + 1, c, i + 1) or # down
|
||||
dfs(r - 1, c, i + 1) or # up
|
||||
dfs(r, c + 1, i + 1) or # right
|
||||
dfs(r, c - 1, i + 1) # left
|
||||
)
|
||||
|
||||
# Restore (backtrack)
|
||||
board[r][c] = temp
|
||||
# Restore cell (backtrack)
|
||||
board[r][c] = original
|
||||
|
||||
return found
|
||||
|
||||
# Try every cell as starting point
|
||||
for r in range(rows):
|
||||
for c in range(cols):
|
||||
if dfs(r, c, 0):
|
||||
@@ -120,5 +172,8 @@ solutions:
|
||||
|
||||
return False
|
||||
explanation: |
|
||||
Try starting from each cell. Use DFS to match characters one by one.
|
||||
Mark cells temporarily, then restore when backtracking.
|
||||
**Time Complexity:** O(m × n × 3^L) — Each starting cell can explore 3 directions per character.
|
||||
|
||||
**Space Complexity:** O(L) — Recursion depth equals word length.
|
||||
|
||||
We try each cell as a starting point. DFS matches characters one by one, marking cells to prevent reuse. After exploring, we restore the cell's value (backtrack) to allow other paths to use it. Short-circuit OR provides early termination.
|
||||
|
||||
Reference in New Issue
Block a user