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.