Bird
Raised Fist0
Interview PrepbacktrackingmediumAmazonFacebookGoogleBloomberg

Word Search

Choose your preparation mode4 modes available
🎯
Word Search
mediumBACKTRACKINGAmazonFacebookGoogle

Imagine you have a crossword puzzle grid and a word list. You want to find if a particular word can be traced in the grid by moving horizontally or vertically, letter by letter.

💡 This problem is about searching for a sequence of characters in a 2D grid by exploring all possible paths. Beginners often struggle because it requires careful backtracking and marking visited cells to avoid revisiting and infinite loops.
📋
Problem Statement

Given a 2D board of characters and a word, determine if the 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.

1 ≤ board.length ≤ 2001 ≤ board[i].length ≤ 2001 ≤ word.length ≤ 10^3board and word consist only of uppercase and lowercase English letters
💡
Example
Input"board = [['A','B','C','E'],['S','F','C','S'],['A','D','E','E']], word = 'ABCCED'"
Outputtrue

The word 'ABCCED' can be found by the path A->B->C->C->E->D in the grid.

Input"board = [['A','B','C','E'],['S','F','C','S'],['A','D','E','E']], word = 'SEE'"
Outputtrue

The word 'SEE' can be found by the path S->E->E.

Input"board = [['A','B','C','E'],['S','F','C','S'],['A','D','E','E']], word = 'ABCB'"
Outputfalse

The word 'ABCB' cannot be found because the letter 'B' cannot be reused.

  • Empty board → false
  • Word longer than total cells → false
  • Word is a single letter present in board → true
  • Board with all identical letters and word repeating that letter → true if length fits
⚠️
Common Mistakes
Not marking visited cells properly

Leads to revisiting cells and infinite recursion or incorrect matches

✅ Mark cells as visited before recursion and restore after (backtracking)

Not checking boundary conditions before accessing board

Causes index out of bounds runtime errors

✅ Always check row and column indices before accessing board cells

Trying to reuse the same cell multiple times in one path

Incorrectly finds words that are not valid according to problem rules

✅ Use visited marking to prevent reusing cells in the current path

Not returning early when word is found

Unnecessary exploration leads to timeouts

✅ Return true immediately when all characters matched

Modifying the board permanently without restoring

Subsequent searches fail due to corrupted board state

✅ Restore the board cell after backtracking

🧠
Brute Force (Pure Recursion with Backtracking)
💡 This approach is the foundation for understanding the problem. It tries every possible path starting from each cell, which is simple but inefficient.

Intuition

Try to find the word starting from every cell. For each cell, recursively explore its neighbors to match the next character, marking cells visited to avoid reuse.

Algorithm

  1. For each cell in the grid, start a DFS if the cell matches the first character of the word.
  2. In DFS, check if the current cell matches the current character in the word.
  3. Mark the cell as visited to avoid revisiting in the current path.
  4. Recursively explore all 4 directions (up, down, left, right) for the next character.
  5. If all characters are matched, return true; else backtrack and unmark the cell.
💡 The challenge is to carefully manage visited cells and backtrack correctly to explore all paths without repetition.
</>
Code
from typing import List

class Solution:
    def exist(self, board: List[List[str]], word: str) -> bool:
        rows, cols = len(board), len(board[0])
        
        def dfs(r, c, index):
            if index == len(word):
                return True
            if r < 0 or r >= rows or c < 0 or c >= cols or board[r][c] != word[index]:
                return False
            temp = board[r][c]
            board[r][c] = '#'
            found = (dfs(r+1, c, index+1) or dfs(r-1, c, index+1) or
                     dfs(r, c+1, index+1) or dfs(r, c-1, index+1))
            board[r][c] = temp
            return found
        
        for i in range(rows):
            for j in range(cols):
                if board[i][j] == word[0] and dfs(i, j, 0):
                    return True
        return False

# Example usage:
# sol = Solution()
# print(sol.exist([['A','B','C','E'],['S','F','C','S'],['A','D','E','E']], 'ABCCED'))  # True
Line Notes
def exist(self, board: List[List[str]], word: str) -> bool:Defines the main function with board and word inputs
rows, cols = len(board), len(board[0])Store dimensions to avoid repeated calls
def dfs(r, c, index):Helper function to perform DFS from cell (r,c) matching word[index]
if index == len(word):Base case: all characters matched successfully
if r < 0 or r >= rows or c < 0 or c >= cols or board[r][c] != word[index]:Boundary and character mismatch check
temp = board[r][c]Save current cell character before marking visited
board[r][c] = '#'Mark cell as visited to avoid reuse in current path
found = (dfs(r+1, c, index+1) or dfs(r-1, c, index+1) or dfs(r, c+1, index+1) or dfs(r, c-1, index+1))Explore all 4 directions recursively
board[r][c] = tempRestore cell after exploring all paths (backtracking)
for i in range(rows):Try starting DFS from every cell in the grid
if board[i][j] == word[0] and dfs(i, j, 0):Start DFS only if first character matches
public class Solution {
    public boolean exist(char[][] board, String word) {
        int rows = board.length, cols = board[0].length;
        
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                if (board[i][j] == word.charAt(0) && dfs(board, word, i, j, 0)) {
                    return true;
                }
            }
        }
        return false;
    }
    
    private boolean dfs(char[][] board, String word, int r, int c, int index) {
        if (index == word.length()) return true;
        if (r < 0 || r >= board.length || c < 0 || c >= board[0].length || board[r][c] != word.charAt(index))
            return false;
        char temp = board[r][c];
        board[r][c] = '#';
        boolean found = dfs(board, word, r + 1, c, index + 1) ||
                        dfs(board, word, r - 1, c, index + 1) ||
                        dfs(board, word, r, c + 1, index + 1) ||
                        dfs(board, word, r, c - 1, index + 1);
        board[r][c] = temp;
        return found;
    }

    // Example usage:
    // public static void main(String[] args) {
    //     Solution sol = new Solution();
    //     char[][] board = {{'A','B','C','E'},{'S','F','C','S'},{'A','D','E','E'}};
    //     System.out.println(sol.exist(board, "ABCCED")); // true
    // }
}
Line Notes
public boolean exist(char[][] board, String word)Main method signature for the problem
for (int i = 0; i < rows; i++)Iterate over each row to start DFS
if (board[i][j] == word.charAt(0) && dfs(board, word, i, j, 0))Start DFS only if first character matches
private boolean dfs(char[][] board, String word, int r, int c, int index)DFS helper to explore paths
if (index == word.length()) return true;Base case: all characters matched
if (r < 0 || r >= board.length || c < 0 || c >= board[0].length || board[r][c] != word.charAt(index)) return false;Boundary and mismatch check
char temp = board[r][c];Save current cell before marking visited
board[r][c] = '#';Mark cell visited to avoid reuse
boolean found = dfs(...) || dfs(...) || dfs(...) || dfs(...)Explore all 4 directions recursively
board[r][c] = temp;Restore cell after recursion (backtracking)
#include <vector>
#include <string>
using namespace std;

class Solution {
public:
    bool exist(vector<vector<char>>& board, string word) {
        int rows = board.size(), cols = board[0].size();
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                if (board[i][j] == word[0] && dfs(board, word, i, j, 0))
                    return true;
            }
        }
        return false;
    }

private:
    bool dfs(vector<vector<char>>& board, const string& word, int r, int c, int index) {
        if (index == word.size()) return true;
        if (r < 0 || r >= (int)board.size() || c < 0 || c >= (int)board[0].size() || board[r][c] != word[index])
            return false;
        char temp = board[r][c];
        board[r][c] = '#';
        bool found = dfs(board, word, r + 1, c, index + 1) ||
                     dfs(board, word, r - 1, c, index + 1) ||
                     dfs(board, word, r, c + 1, index + 1) ||
                     dfs(board, word, r, c - 1, index + 1);
        board[r][c] = temp;
        return found;
    }

// Example usage:
// int main() {
//     Solution sol;
//     vector<vector<char>> board = {{'A','B','C','E'},{'S','F','C','S'},{'A','D','E','E'}};
//     bool res = sol.exist(board, "ABCCED");
//     return 0;
// }
Line Notes
bool exist(vector<vector<char>>& board, string word)Main function signature
for (int i = 0; i < rows; i++)Loop over rows to start DFS
if (board[i][j] == word[0] && dfs(board, word, i, j, 0))Start DFS only if first character matches
bool dfs(vector<vector<char>>& board, const string& word, int r, int c, int index)DFS helper function
if (index == word.size()) return true;Base case: all characters matched
if (r < 0 || r >= (int)board.size() || c < 0 || c >= (int)board[0].size() || board[r][c] != word[index]) return false;Boundary and mismatch check
char temp = board[r][c];Save current cell before marking visited
board[r][c] = '#';Mark cell visited to avoid reuse
bool found = dfs(...) || dfs(...) || dfs(...) || dfs(...)Explore all 4 directions recursively
board[r][c] = temp;Restore cell after recursion (backtracking)
var exist = function(board, word) {
    const rows = board.length, cols = board[0].length;
    
    function dfs(r, c, index) {
        if (index === word.length) return true;
        if (r < 0 || r >= rows || c < 0 || c >= cols || board[r][c] !== word[index]) return false;
        const temp = board[r][c];
        board[r][c] = '#';
        const found = dfs(r + 1, c, index + 1) || dfs(r - 1, c, index + 1) || dfs(r, c + 1, index + 1) || dfs(r, c - 1, index + 1);
        board[r][c] = temp;
        return found;
    }
    
    for (let i = 0; i < rows; i++) {
        for (let j = 0; j < cols; j++) {
            if (board[i][j] === word[0] && dfs(i, j, 0)) return true;
        }
    }
    return false;
};

// Example usage:
// console.log(exist([['A','B','C','E'],['S','F','C','S'],['A','D','E','E']], 'ABCCED')); // true
Line Notes
var exist = function(board, word)Main function declaration
const rows = board.length, cols = board[0].length;Store board dimensions
function dfs(r, c, index)DFS helper function to explore paths
if (index === word.length) return true;Base case: all characters matched
if (r < 0 || r >= rows || c < 0 || c >= cols || board[r][c] !== word[index]) return false;Boundary and mismatch check
const temp = board[r][c];Save current cell before marking visited
board[r][c] = '#';Mark cell visited to avoid reuse
const found = dfs(...) || dfs(...) || dfs(...) || dfs(...)Explore all 4 directions recursively
board[r][c] = temp;Restore cell after recursion (backtracking)
for (let i = 0; i < rows; i++)Try starting DFS from every cell
⏱
Complexity
TimeO(N * 3^L) where N is number of cells and L is length of the word
SpaceO(L) recursion stack for the word length

From each cell, we explore up to 4 directions but avoid going back, so roughly 3 directions per step for L steps, repeated for each cell

💡 For a 10x10 board and word length 5, this could mean up to 100 * 3^5 = 24300 operations, which is feasible but grows quickly.
Interview Verdict: Accepted but inefficient for large inputs

This approach works but is slow for large boards or long words, so we need to optimize.

🧠
Backtracking with Early Pruning Using Character Frequency
💡 This approach improves brute force by quickly rejecting impossible searches based on character counts, reducing unnecessary DFS calls.

Intuition

Before searching, count characters in the board and the word. If the board lacks enough occurrences of any character, return false immediately.

Algorithm

  1. Count frequency of each character in the board and in the word.
  2. If any character in the word appears more times than in the board, return false immediately.
  3. Otherwise, perform the same DFS backtracking as brute force.
  4. Optionally, reverse the word if the last character is rarer than the first to reduce search space.
💡 This pruning step can save a lot of time by avoiding DFS when the word is impossible to form.
</>
Code
from typing import List
from collections import Counter

class Solution:
    def exist(self, board: List[List[str]], word: str) -> bool:
        rows, cols = len(board), len(board[0])
        board_count = Counter(c for row in board for c in row)
        word_count = Counter(word)
        for c in word_count:
            if word_count[c] > board_count.get(c, 0):
                return False
        # Optional: reverse word if last char rarer than first
        if board_count[word[0]] > board_count[word[-1]]:
            word = word[::-1]
        
        def dfs(r, c, index):
            if index == len(word):
                return True
            if r < 0 or r >= rows or c < 0 or c >= cols or board[r][c] != word[index]:
                return False
            temp = board[r][c]
            board[r][c] = '#'
            found = (dfs(r+1, c, index+1) or dfs(r-1, c, index+1) or
                     dfs(r, c+1, index+1) or dfs(r, c-1, index+1))
            board[r][c] = temp
            return found
        
        for i in range(rows):
            for j in range(cols):
                if board[i][j] == word[0] and dfs(i, j, 0):
                    return True
        return False

# Example usage:
# sol = Solution()
# print(sol.exist([['A','B','C','E'],['S','F','C','S'],['A','D','E','E']], 'ABCCED'))  # True
Line Notes
board_count = Counter(c for row in board for c in row)Count all characters in the board
word_count = Counter(word)Count characters in the word
for c in word_count:Check if board has enough of each character
if word_count[c] > board_count.get(c, 0):If board lacks characters, return false early
if board_count[word[0]] > board_count[word[-1]]:Heuristic to reverse word if last char is rarer
def dfs(r, c, index):DFS helper function same as brute force
temp = board[r][c]Save cell before marking visited
board[r][c] = '#'Mark visited
found = (dfs(...) or ... )Explore all 4 directions
board[r][c] = tempBacktrack by restoring cell
import java.util.*;

public class Solution {
    public boolean exist(char[][] board, String word) {
        int rows = board.length, cols = board[0].length;
        Map<Character, Integer> boardCount = new HashMap<>();
        for (char[] row : board) {
            for (char c : row) {
                boardCount.put(c, boardCount.getOrDefault(c, 0) + 1);
            }
        }
        Map<Character, Integer> wordCount = new HashMap<>();
        for (char c : word.toCharArray()) {
            wordCount.put(c, wordCount.getOrDefault(c, 0) + 1);
        }
        for (char c : wordCount.keySet()) {
            if (wordCount.get(c) > boardCount.getOrDefault(c, 0)) return false;
        }
        if (boardCount.get(word.charAt(0)) > boardCount.get(word.charAt(word.length() - 1))) {
            word = new StringBuilder(word).reverse().toString();
        }
        
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                if (board[i][j] == word.charAt(0) && dfs(board, word, i, j, 0)) {
                    return true;
                }
            }
        }
        return false;
    }
    
    private boolean dfs(char[][] board, String word, int r, int c, int index) {
        if (index == word.length()) return true;
        if (r < 0 || r >= board.length || c < 0 || c >= board[0].length || board[r][c] != word.charAt(index))
            return false;
        char temp = board[r][c];
        board[r][c] = '#';
        boolean found = dfs(board, word, r + 1, c, index + 1) ||
                        dfs(board, word, r - 1, c, index + 1) ||
                        dfs(board, word, r, c + 1, index + 1) ||
                        dfs(board, word, r, c - 1, index + 1);
        board[r][c] = temp;
        return found;
    }

    // Example usage:
    // public static void main(String[] args) {
    //     Solution sol = new Solution();
    //     char[][] board = {{'A','B','C','E'},{'S','F','C','S'},{'A','D','E','E'}};
    //     System.out.println(sol.exist(board, "ABCCED")); // true
    // }
}
Line Notes
Map<Character, Integer> boardCount = new HashMap<>();Count characters in board
for (char c : word.toCharArray())Count characters in word
if (wordCount.get(c) > boardCount.getOrDefault(c, 0)) return false;Early pruning if board lacks chars
if (boardCount.get(word.charAt(0)) > boardCount.get(word.charAt(word.length() - 1)))Heuristic to reverse word
private boolean dfs(char[][] board, String word, int r, int c, int index)DFS helper same as brute force
char temp = board[r][c];Save cell before marking visited
board[r][c] = '#';Mark visited
boolean found = dfs(...) || ...Explore all 4 directions
board[r][c] = temp;Backtrack by restoring cell
#include <vector>
#include <string>
#include <unordered_map>
using namespace std;

class Solution {
public:
    bool exist(vector<vector<char>>& board, string word) {
        unordered_map<char, int> boardCount, wordCount;
        for (auto& row : board) {
            for (char c : row) boardCount[c]++;
        }
        for (char c : word) wordCount[c]++;
        for (auto& p : wordCount) {
            if (boardCount[p.first] < p.second) return false;
        }
        if (boardCount[word[0]] > boardCount[word.back()]) {
            reverse(word.begin(), word.end());
        }
        int rows = board.size(), cols = board[0].size();
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                if (board[i][j] == word[0] && dfs(board, word, i, j, 0))
                    return true;
            }
        }
        return false;
    }

private:
    bool dfs(vector<vector<char>>& board, const string& word, int r, int c, int index) {
        if (index == (int)word.size()) return true;
        if (r < 0 || r >= (int)board.size() || c < 0 || c >= (int)board[0].size() || board[r][c] != word[index])
            return false;
        char temp = board[r][c];
        board[r][c] = '#';
        bool found = dfs(board, word, r + 1, c, index + 1) ||
                     dfs(board, word, r - 1, c, index + 1) ||
                     dfs(board, word, r, c + 1, index + 1) ||
                     dfs(board, word, r, c - 1, index + 1);
        board[r][c] = temp;
        return found;
    }

// Example usage:
// int main() {
//     Solution sol;
//     vector<vector<char>> board = {{'A','B','C','E'},{'S','F','C','S'},{'A','D','E','E'}};
//     bool res = sol.exist(board, "ABCCED");
//     return 0;
// }
Line Notes
unordered_map<char, int> boardCount, wordCount;Count characters in board and word
for (auto& p : wordCount)Check if board has enough characters
if (boardCount[word[0]] > boardCount[word.back()])Heuristic to reverse word
bool dfs(vector<vector<char>>& board, const string& word, int r, int c, int index)DFS helper same as brute force
char temp = board[r][c];Save cell before marking visited
board[r][c] = '#';Mark visited
bool found = dfs(...) || ...Explore all 4 directions
board[r][c] = temp;Backtrack by restoring cell
var exist = function(board, word) {
    const rows = board.length, cols = board[0].length;
    const boardCount = {};
    for (let r = 0; r < rows; r++) {
        for (let c = 0; c < cols; c++) {
            boardCount[board[r][c]] = (boardCount[board[r][c]] || 0) + 1;
        }
    }
    const wordCount = {};
    for (const ch of word) {
        wordCount[ch] = (wordCount[ch] || 0) + 1;
    }
    for (const ch in wordCount) {
        if (!boardCount[ch] || wordCount[ch] > boardCount[ch]) return false;
    }
    if (boardCount[word[0]] > boardCount[word[word.length - 1]]) {
        word = word.split('').reverse().join('');
    }
    
    function dfs(r, c, index) {
        if (index === word.length) return true;
        if (r < 0 || r >= rows || c < 0 || c >= cols || board[r][c] !== word[index]) return false;
        const temp = board[r][c];
        board[r][c] = '#';
        const found = dfs(r + 1, c, index + 1) || dfs(r - 1, c, index + 1) || dfs(r, c + 1, index + 1) || dfs(r, c - 1, index + 1);
        board[r][c] = temp;
        return found;
    }
    
    for (let i = 0; i < rows; i++) {
        for (let j = 0; j < cols; j++) {
            if (board[i][j] === word[0] && dfs(i, j, 0)) return true;
        }
    }
    return false;
};

// Example usage:
// console.log(exist([['A','B','C','E'],['S','F','C','S'],['A','D','E','E']], 'ABCCED')); // true
Line Notes
const boardCount = {};Count characters in board
const wordCount = {};Count characters in word
if (!boardCount[ch] || wordCount[ch] > boardCount[ch]) return false;Early pruning if board lacks chars
if (boardCount[word[0]] > boardCount[word[word.length - 1]])Heuristic to reverse word
function dfs(r, c, index)DFS helper same as brute force
const temp = board[r][c];Save cell before marking visited
board[r][c] = '#';Mark visited
const found = dfs(...) || ...Explore all 4 directions
board[r][c] = temp;Backtrack by restoring cell
⏱
Complexity
TimeStill O(N * 3^L) but often faster due to pruning
SpaceO(L) recursion stack

Pruning avoids many DFS calls when word characters are missing or insufficient in the board

💡 This optimization can drastically reduce runtime on impossible cases, making it practical for larger inputs.
Interview Verdict: Accepted and faster than brute force on many inputs

This approach is a practical improvement and often sufficient in interviews.

🧠
Backtracking with In-Place Marking and Direction Array
💡 This approach cleans up the code by using a directions array to reduce repetition and marks visited cells in-place for space efficiency.

Intuition

Use a fixed array of direction vectors to explore neighbors, making the code concise and less error-prone.

Algorithm

  1. Initialize a directions array with vectors for up, down, left, right.
  2. For each cell matching the first character, start DFS.
  3. In DFS, mark the cell visited by replacing its character with a sentinel.
  4. Explore neighbors using the directions array.
  5. Backtrack by restoring the original character.
💡 This approach is functionally similar to brute force but improves code clarity and reduces bugs.
</>
Code
from typing import List

class Solution:
    def exist(self, board: List[List[str]], word: str) -> bool:
        rows, cols = len(board), len(board[0])
        directions = [(1,0), (-1,0), (0,1), (0,-1)]
        
        def dfs(r, c, index):
            if index == len(word):
                return True
            if r < 0 or r >= rows or c < 0 or c >= cols or board[r][c] != word[index]:
                return False
            temp = board[r][c]
            board[r][c] = '#'
            for dr, dc in directions:
                if dfs(r + dr, c + dc, index + 1):
                    board[r][c] = temp
                    return True
            board[r][c] = temp
            return False
        
        for i in range(rows):
            for j in range(cols):
                if board[i][j] == word[0] and dfs(i, j, 0):
                    return True
        return False

# Example usage:
# sol = Solution()
# print(sol.exist([['A','B','C','E'],['S','F','C','S'],['A','D','E','E']], 'ABCCED'))  # True
Line Notes
directions = [(1,0), (-1,0), (0,1), (0,-1)]Predefined moves for neighbors to avoid repetition
def dfs(r, c, index):DFS helper function
if index == len(word):Base case: all characters matched
for dr, dc in directions:Loop over all four directions
board[r][c] = '#'Mark cell visited
board[r][c] = tempRestore cell after recursion (backtracking)
if dfs(r + dr, c + dc, index + 1):Return early if path found
public class Solution {
    private final int[][] directions = {{1,0}, {-1,0}, {0,1}, {0,-1}};
    public boolean exist(char[][] board, String word) {
        int rows = board.length, cols = board[0].length;
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                if (board[i][j] == word.charAt(0) && dfs(board, word, i, j, 0)) {
                    return true;
                }
            }
        }
        return false;
    }
    
    private boolean dfs(char[][] board, String word, int r, int c, int index) {
        if (index == word.length()) return true;
        if (r < 0 || r >= board.length || c < 0 || c >= board[0].length || board[r][c] != word.charAt(index))
            return false;
        char temp = board[r][c];
        board[r][c] = '#';
        for (int[] d : directions) {
            if (dfs(board, word, r + d[0], c + d[1], index + 1)) {
                board[r][c] = temp;
                return true;
            }
        }
        board[r][c] = temp;
        return false;
    }

    // Example usage:
    // public static void main(String[] args) {
    //     Solution sol = new Solution();
    //     char[][] board = {{'A','B','C','E'},{'S','F','C','S'},{'A','D','E','E'}};
    //     System.out.println(sol.exist(board, "ABCCED")); // true
    // }
}
Line Notes
private final int[][] directions = {{1,0}, {-1,0}, {0,1}, {0,-1}};Store directions to avoid code duplication
for (int[] d : directions)Iterate over all four directions
char temp = board[r][c];Save current cell before marking visited
board[r][c] = '#';Mark cell visited
if (dfs(board, word, r + d[0], c + d[1], index + 1))Return early if path found
board[r][c] = temp;Restore cell after recursion
#include <vector>
#include <string>
using namespace std;

class Solution {
    const vector<pair<int,int>> directions = {{1,0}, {-1,0}, {0,1}, {0,-1}};
public:
    bool exist(vector<vector<char>>& board, string word) {
        int rows = board.size(), cols = board[0].size();
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                if (board[i][j] == word[0] && dfs(board, word, i, j, 0))
                    return true;
            }
        }
        return false;
    }

private:
    bool dfs(vector<vector<char>>& board, const string& word, int r, int c, int index) {
        if (index == (int)word.size()) return true;
        if (r < 0 || r >= (int)board.size() || c < 0 || c >= (int)board[0].size() || board[r][c] != word[index])
            return false;
        char temp = board[r][c];
        board[r][c] = '#';
        for (auto& d : directions) {
            if (dfs(board, word, r + d.first, c + d.second, index + 1)) {
                board[r][c] = temp;
                return true;
            }
        }
        board[r][c] = temp;
        return false;
    }

// Example usage:
// int main() {
//     Solution sol;
//     vector<vector<char>> board = {{'A','B','C','E'},{'S','F','C','S'},{'A','D','E','E'}};
//     bool res = sol.exist(board, "ABCCED");
//     return 0;
// }
Line Notes
const vector<pair<int,int>> directions = {{1,0}, {-1,0}, {0,1}, {0,-1}};Predefined directions for neighbors
for (auto& d : directions)Loop over all directions
char temp = board[r][c];Save cell before marking visited
board[r][c] = '#';Mark visited
if (dfs(board, word, r + d.first, c + d.second, index + 1))Return early if path found
board[r][c] = temp;Restore cell after recursion
var exist = function(board, word) {
    const rows = board.length, cols = board[0].length;
    const directions = [[1,0], [-1,0], [0,1], [0,-1]];
    
    function dfs(r, c, index) {
        if (index === word.length) return true;
        if (r < 0 || r >= rows || c < 0 || c >= cols || board[r][c] !== word[index]) return false;
        const temp = board[r][c];
        board[r][c] = '#';
        for (const [dr, dc] of directions) {
            if (dfs(r + dr, c + dc, index + 1)) {
                board[r][c] = temp;
                return true;
            }
        }
        board[r][c] = temp;
        return false;
    }
    
    for (let i = 0; i < rows; i++) {
        for (let j = 0; j < cols; j++) {
            if (board[i][j] === word[0] && dfs(i, j, 0)) return true;
        }
    }
    return false;
};

// Example usage:
// console.log(exist([['A','B','C','E'],['S','F','C','S'],['A','D','E','E']], 'ABCCED')); // true
Line Notes
const directions = [[1,0], [-1,0], [0,1], [0,-1]];Store directions to avoid code duplication
for (const [dr, dc] of directions)Iterate over all four directions
const temp = board[r][c];Save cell before marking visited
board[r][c] = '#';Mark cell visited
if (dfs(r + dr, c + dc, index + 1))Return early if path found
board[r][c] = temp;Restore cell after recursion
⏱
Complexity
TimeO(N * 3^L) same as brute force
SpaceO(L) recursion stack

Using directions array does not change complexity but improves code clarity and reduces bugs

💡 This is the preferred way to implement backtracking in interviews for readability and maintainability.
Interview Verdict: Accepted and clean code

This approach is recommended for interviews due to clarity and correctness.

📊
All Approaches - One-Glance Tradeoffs
💡 Approach 3 is recommended for interviews due to clarity and efficiency. Approach 2 is useful for optimization. Approach 1 is good for initial understanding.
ApproachTimeSpaceStack RiskReconstructUse In Interview
1. Brute ForceO(N * 3^L)O(L)Yes (deep recursion possible)N/AMention only - never code
2. Backtracking with Early PruningO(N * 3^L) but faster in practiceO(L)YesN/AMention as optimization
3. Backtracking with Directions ArrayO(N * 3^L)O(L)YesN/ACode this approach
💼
Interview Strategy
💡 Use this guide to understand the problem deeply before your interview. Start by clarifying the problem, then explain brute force, optimize, and finally write clean code.

How to Present

Clarify input constraints and ask about board size and character set.State the brute force backtracking approach and its complexity.Discuss pruning optimizations and how to improve runtime.Explain how to write clean code using direction arrays and in-place marking.Write code carefully and test with edge cases.

Time Allocation

Clarify: 3min → Approach: 5min → Code: 10min → Test: 5min. Total ~23min

What the Interviewer Tests

The interviewer tests your ability to implement backtracking correctly, handle edge cases, optimize search, and write clean, bug-free code.

Common Follow-ups

  • How to find all words from a list in the board? → Use Trie + backtracking
  • What if diagonal moves are allowed? → Add diagonal directions to the directions array
💡 These follow-ups test your ability to extend the solution and adapt to variations.
🔍
Pattern Recognition

When to Use

1) You have a grid or matrix input, 2) You need to find a path or sequence, 3) Movement is restricted to neighbors, 4) You must avoid revisiting cells.

Signature Phrases

word can be constructed from letters of sequentially adjacent cellsthe same letter cell may not be used more than once

NOT This Pattern When

Problems that allow revisiting cells freely or do not require path constraints (e.g., flood fill) are different.

Similar Problems

Word Search II - find multiple words using Trie + backtrackingSudoku Solver - backtracking on grid with constraintsN-Queens - backtracking with constraint satisfaction

Practice

(1/5)
1. Consider the following BFS-based code snippet for removing invalid parentheses:
from collections import deque

def is_valid(s: str) -> bool:
    count = 0
    for c in s:
        if c == '(': count += 1
        elif c == ')':
            count -= 1
            if count < 0:
                return False
    return count == 0

def remove_invalid_parentheses_bfs(s: str):
    res = []
    visited = set([s])
    queue = deque([s])
    found = False

    while queue:
        size = len(queue)
        for _ in range(size):
            curr = queue.popleft()
            if is_valid(curr):
                res.append(curr)
                found = True
            if found:
                continue
            for i in range(len(curr)):
                if curr[i] not in ('(', ')'):
                    continue
                next_str = curr[:i] + curr[i+1:]
                if next_str not in visited:
                    visited.add(next_str)
                    queue.append(next_str)
    return res

print(remove_invalid_parentheses_bfs("()())()"))
What is the output of this code?
easy
A. ["()()()"]
B. ["()()()", "(())()"]
C. ["(())()"]
D. ["()())()"]

Solution

  1. Step 1: Trace BFS levels for input "()())()"

    Initial string is invalid. BFS removes one parenthesis at each position generating candidates. The first valid strings found are "()()()" and "(())()".
  2. Step 2: Confirm both valid strings are collected

    Since BFS stops adding new states after finding valid strings at current level, both minimal removal results are returned.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    Both minimal valid strings are returned [OK]
Hint: BFS returns all minimal valid strings at first valid level [OK]
Common Mistakes:
  • Returning only one valid string
  • Returning original invalid string
  • Missing one minimal valid string
2. What is the time complexity of the optimal backtracking algorithm that generates unique permutations of an array with duplicates by sorting and skipping duplicates during recursion?
medium
A. O(n! * log n) due to sorting and binary search for duplicates
B. O(n! * n^2) because each permutation requires copying the array and checking duplicates
C. O(n^n) because the recursion explores all possible swaps without pruning
D. O(n! * n) because pruning duplicates early reduces redundant branches but each permutation still requires O(n) copying

Solution

  1. Step 1: Identify complexity of outer and inner loops

    Backtracking explores permutations, which is O(n!). Each permutation requires copying O(n) elements to result.
  2. Step 2: Check if pruning duplicates reduces complexity

    Pruning duplicates early avoids redundant branches, so complexity is O(n! * n), not worse. Sorting is O(n log n) but done once.
  3. Final Answer:

    Option D -> Option D
  4. Quick Check:

    Pruning reduces branches but copying each permutation costs O(n) [OK]
Hint: Pruning duplicates reduces branches but copying costs O(n) per permutation [OK]
Common Mistakes:
  • Confusing pruning effect or ignoring copy cost
3. The following code attempts to generate all permutations of a list using Heap's algorithm but contains a subtle bug:
def permute(nums):
    res = [nums[:]]
    c = [0] * len(nums)
    i = 0
    while i < len(nums):
        if c[i] < i:
            if i % 2 == 0:
                nums[0], nums[i] = nums[i], nums[0]
            else:
                nums[c[i]], nums[i] = nums[i], nums[c[i]]
            res.append(nums)
            c[i] += 1
            i = 0
        else:
            c[i] = 0
            i += 1
    return res
What is the bug in this code?
medium
A. The used array c is not reset properly, causing infinite loop
B. Appending nums directly without copying causes all entries in res to reference the same list
C. Swapping nums[0] and nums[i] when i is even is incorrect logic
D. The initial res list should be empty, not contain nums[:] at start

Solution

  1. Step 1: Identify how results are stored

    The code appends nums directly to res without copying.
  2. Step 2: Understand consequences of appending references

    Since nums is mutated in-place, all entries in res point to the same list, resulting in duplicates of the final permutation.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    Appending a copy nums[:] fixes the bug [OK]
Hint: Always append a copy of mutable lists to results [OK]
Common Mistakes:
  • Forgetting to copy before appending results in duplicate references
4. What is the time complexity of the optimal iterative backtracking solution with pruning for restoring IP addresses from a string of length n, and why?
medium
A. O(3^4) because each of the 4 segments can be length 1 to 3, and pruning limits exploration
B. O(4^n) because each character can start a new segment or continue the previous one
C. O(n^4) because we try all partitions of the string into 4 segments
D. O(n!) due to permutations of segment lengths

Solution

  1. Step 1: Identify branching factor per segment

    Each segment can be length 1, 2, or 3, so branching factor is at most 3 per segment.
  2. Step 2: Calculate total combinations

    There are 4 segments, so total combinations are at most 3^4 = 81. Pruning further reduces this.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Time complexity depends on fixed 4 segments with max 3 choices each [OK]
Hint: Fixed 4 segments with max 3 lengths each -> 3^4 complexity [OK]
Common Mistakes:
  • Assuming complexity depends on n^4
  • Confusing permutations with combinations
5. Suppose you want to find the lexicographically next permutation of an array where elements can be repeated and reused multiple times (infinite supply). Which modification to the standard next permutation algorithm is necessary?
hard
A. No modification needed; the standard next permutation algorithm works as is.
B. Modify the algorithm to generate all permutations with repetition and pick the next one, since in-place approach fails.
C. Adjust the pivot search to consider repeated elements and skip duplicates to avoid redundant swaps.
D. Use a backtracking approach that allows repeated elements and generates permutations with repetition.

Solution

  1. Step 1: Understand the problem variant

    Allowing repeated use of elements means permutations with repetition, which standard next permutation does not handle as it assumes fixed elements.
  2. Step 2: Identify correct approach

    Backtracking that generates permutations with repetition is required, as in-place next permutation only rearranges existing elements without reuse.
  3. Final Answer:

    Option D -> Option D
  4. Quick Check:

    Standard next permutation assumes fixed elements; repeated reuse requires backtracking [OK]
Hint: Next permutation assumes fixed elements; reuse needs backtracking [OK]
Common Mistakes:
  • Thinking standard algorithm handles reuse
  • Trying to modify pivot logic only
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard