medium tree, graph, dp questions

This commit is contained in:
2025-04-28 23:04:27 +01:00
parent be754e41a4
commit c58b0b60aa
4 changed files with 541 additions and 0 deletions

View File

@@ -0,0 +1,124 @@
title: Word Search
slug: word-search
difficulty: medium
leetcode_id: 79
leetcode_url: https://leetcode.com/problems/word-search/
categories:
- arrays
- recursion
patterns:
- backtracking
- dfs
description: |
Given an m x 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.
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
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."
- input: 'board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "SEE"'
output: "true"
explanation: "Path exists."
- 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:
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.
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.
common_pitfalls:
- 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"
- title: Modifying board permanently
description: |
If you change board[r][c] to mark as visited, restore it after backtracking.
- 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.
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)
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.
solutions:
- approach_name: DFS with Backtracking (Optimal)
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:
if i == len(word):
return True
if r < 0 or r >= rows or c < 0 or c >= cols:
return False
if board[r][c] != word[i]:
return False
# Mark as visited
temp = board[r][c]
board[r][c] = '#'
# Explore all 4 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)
)
# Restore (backtrack)
board[r][c] = temp
return found
for r in range(rows):
for c in range(cols):
if dfs(r, c, 0):
return True
return False
explanation: |
Try starting from each cell. Use DFS to match characters one by one.
Mark cells temporarily, then restore when backtracking.