Word Search

medium

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 in a single word path.

Examples

Example 1

  • Input: board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED"
  • Output: true
  • Explanation: The word "ABCCED" can be found following the path A(0,0) -> B(0,1) -> C(0,2) -> C(1,2) -> E(2,2) -> D(2,1).

Example 2

  • Input: board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "SEE"
  • Output: true
  • Explanation: The word "SEE" can be found following the path S(1,3) -> E(2,3) -> E(2,2).

Example 3

  • Input: board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCB"
  • Output: false
  • Explanation: The word "ABCB" cannot be formed because each cell can only be used once per path.

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.

Approaches

2 worked solutions ranked from straightforward to optimal — each with intuition, algorithm, complexity, and verified code in C++, Java, Python, and Go.

Approach 1

Backtracking

Intuition

We try to build the word character by character starting from every cell in the grid. From each cell, we explore all four directions using backtracking. At each step, we check if the current cell matches the expected character in the word. If it does, we mark the cell as visited (to prevent reuse) and recurse to find the next character. If the recursion leads to a dead end, we unmark the cell (backtrack) and try other directions. The search succeeds if we match all characters in the word. We fail early if the current cell does not match or is out of bounds.

Algorithm

  1. Iterate through every cell in the grid as a potential starting position.
  2. For each starting cell, call the backtracking function with the first character index.
  3. In the backtracking function, check if the index equals the word length -- if so, we found the word, return true.
  4. Check bounds, whether the cell matches the current character, and whether it is already visited.
  5. Mark the cell as visited by replacing its value with a sentinel character.
  6. Recursively try all four adjacent cells with the next character index.
  7. Restore the cell's original value (backtrack) and return the result.
TimeO(m * n * 3^L) where L is the word length, since each cell branches into at most 3 directions (excluding where we came from)SpaceO(L) for the recursion stack depth

Code (C++ · Java · Python · Go)

C++17
class Solution {
public:
    bool exist(vector<vector<char>>& board, string word) {
        int m = board.size(), n = board[0].size();

        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (backtrack(board, word, i, j, 0)) {
                    return true;
                }
            }
        }

        return false;
    }

private:
    bool backtrack(vector<vector<char>>& board, string& word, int r, int c, int idx) {
        if (idx == word.size()) return true;

        int m = board.size(), n = board[0].size();
        if (r < 0 || r >= m || c < 0 || c >= n || board[r][c] != word[idx]) {
            return false;
        }

        char temp = board[r][c];
        board[r][c] = '#';

        bool found = backtrack(board, word, r + 1, c, idx + 1)
                  || backtrack(board, word, r - 1, c, idx + 1)
                  || backtrack(board, word, r, c + 1, idx + 1)
                  || backtrack(board, word, r, c - 1, idx + 1);

        board[r][c] = temp;
        return found;
    }
};
Java 21
class Solution {
    public boolean exist(char[][] board, String word) {
        int m = board.length, n = board[0].length;

        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (backtrack(board, word, i, j, 0)) {
                    return true;
                }
            }
        }

        return false;
    }

    private boolean backtrack(char[][] board, String word, int r, int c, int idx) {
        if (idx == word.length()) return true;

        int m = board.length, n = board[0].length;
        if (r < 0 || r >= m || c < 0 || c >= n || board[r][c] != word.charAt(idx)) {
            return false;
        }

        char temp = board[r][c];
        board[r][c] = '#';

        boolean found = backtrack(board, word, r + 1, c, idx + 1)
                     || backtrack(board, word, r - 1, c, idx + 1)
                     || backtrack(board, word, r, c + 1, idx + 1)
                     || backtrack(board, word, r, c - 1, idx + 1);

        board[r][c] = temp;
        return found;
    }
}
Python 3.12
from typing import List


class Solution:
    def exist(self, board: List[List[str]], word: str) -> bool:
        m, n = len(board), len(board[0])

        def backtrack(r, c, idx):
            if idx == len(word):
                return True

            if r < 0 or r >= m or c < 0 or c >= n or board[r][c] != word[idx]:
                return False

            temp = board[r][c]
            board[r][c] = '#'

            found = (backtrack(r + 1, c, idx + 1)
                  or backtrack(r - 1, c, idx + 1)
                  or backtrack(r, c + 1, idx + 1)
                  or backtrack(r, c - 1, idx + 1))

            board[r][c] = temp
            return found

        for i in range(m):
            for j in range(n):
                if backtrack(i, j, 0):
                    return True

        return False
Go 1.26
func exist(board [][]byte, word string) bool {
    m, n := len(board), len(board[0])

    var backtrack func(r, c, idx int) bool
    backtrack = func(r, c, idx int) bool {
        if idx == len(word) {
            return true
        }

        if r < 0 || r >= m || c < 0 || c >= n || board[r][c] != word[idx] {
            return false
        }

        temp := board[r][c]
        board[r][c] = '#'

        found := backtrack(r+1, c, idx+1) ||
            backtrack(r-1, c, idx+1) ||
            backtrack(r, c+1, idx+1) ||
            backtrack(r, c-1, idx+1)

        board[r][c] = temp
        return found
    }

    for i := 0; i < m; i++ {
        for j := 0; j < n; j++ {
            if backtrack(i, j, 0) {
                return true
            }
        }
    }

    return false
}
Approach 2

Backtracking with Pruning

Intuition

We enhance the basic backtracking approach with a frequency-based pruning optimization. Before starting the search, we count the frequency of each character in the board and check if the board contains enough of each character needed by the word. Additionally, if the first character of the word is less frequent in the board than the last character, we reverse the word before searching -- this reduces the number of starting positions and leads to earlier termination of dead-end paths. These pruning steps can dramatically reduce runtime on boards where the word's characters are sparse.

Algorithm

  1. Count character frequencies in the board. For each character in the word, verify the board has enough occurrences. If not, return false immediately.
  2. Compare the frequency of the first and last characters of the word. If the last character is less frequent, reverse the word to start from the rarer character.
  3. Iterate through every cell as a potential starting point.
  4. Run the same backtracking as the basic approach: mark cells with a sentinel, recurse in four directions, and restore on backtrack.
  5. Return true if any starting cell leads to a complete match.
TimeO(m * n * 3^L) worst case, but typically much faster due to pruningSpaceO(L) for the recursion stack depth

Code (C++ · Java · Python · Go)

C++17
class Solution {
public:
    bool exist(vector<vector<char>>& board, string word) {
        int m = board.size(), n = board[0].size();

        unordered_map<char, int> boardCount;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                boardCount[board[i][j]]++;
            }
        }

        unordered_map<char, int> wordCount;
        for (char c : word) wordCount[c]++;

        for (auto& [c, cnt] : wordCount) {
            if (boardCount[c] < cnt) return false;
        }

        if (boardCount[word[0]] > boardCount[word.back()]) {
            reverse(word.begin(), word.end());
        }

        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (backtrack(board, word, i, j, 0)) {
                    return true;
                }
            }
        }

        return false;
    }

private:
    bool backtrack(vector<vector<char>>& board, string& word, int r, int c, int idx) {
        if (idx == word.size()) return true;

        int m = board.size(), n = board[0].size();
        if (r < 0 || r >= m || c < 0 || c >= n || board[r][c] != word[idx]) {
            return false;
        }

        char temp = board[r][c];
        board[r][c] = '#';

        bool found = backtrack(board, word, r + 1, c, idx + 1)
                  || backtrack(board, word, r - 1, c, idx + 1)
                  || backtrack(board, word, r, c + 1, idx + 1)
                  || backtrack(board, word, r, c - 1, idx + 1);

        board[r][c] = temp;
        return found;
    }
};
Java 21
class Solution {
    public boolean exist(char[][] board, String word) {
        int m = board.length, n = board[0].length;

        Map<Character, Integer> boardCount = new HashMap<>();
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                boardCount.merge(board[i][j], 1, Integer::sum);
            }
        }

        Map<Character, Integer> wordCount = new HashMap<>();
        for (char c : word.toCharArray()) {
            wordCount.merge(c, 1, Integer::sum);
        }

        for (var entry : wordCount.entrySet()) {
            if (boardCount.getOrDefault(entry.getKey(), 0) < entry.getValue()) {
                return false;
            }
        }

        char[] chars = word.toCharArray();
        if (boardCount.getOrDefault(chars[0], 0) > boardCount.getOrDefault(chars[chars.length - 1], 0)) {
            for (int i = 0, j = chars.length - 1; i < j; i++, j--) {
                char temp = chars[i];
                chars[i] = chars[j];
                chars[j] = temp;
            }
        }
        String searchWord = new String(chars);

        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (backtrack(board, searchWord, i, j, 0)) {
                    return true;
                }
            }
        }

        return false;
    }

    private boolean backtrack(char[][] board, String word, int r, int c, int idx) {
        if (idx == word.length()) return true;

        int m = board.length, n = board[0].length;
        if (r < 0 || r >= m || c < 0 || c >= n || board[r][c] != word.charAt(idx)) {
            return false;
        }

        char temp = board[r][c];
        board[r][c] = '#';

        boolean found = backtrack(board, word, r + 1, c, idx + 1)
                     || backtrack(board, word, r - 1, c, idx + 1)
                     || backtrack(board, word, r, c + 1, idx + 1)
                     || backtrack(board, word, r, c - 1, idx + 1);

        board[r][c] = temp;
        return found;
    }
}
Python 3.12
from typing import List
from collections import Counter


class Solution:
    def exist(self, board: List[List[str]], word: str) -> bool:
        m, n = len(board), len(board[0])

        board_count = Counter()
        for i in range(m):
            for j in range(n):
                board_count[board[i][j]] += 1

        word_count = Counter(word)
        for c, cnt in word_count.items():
            if board_count[c] < cnt:
                return False

        if board_count[word[0]] > board_count[word[-1]]:
            word = word[::-1]

        def backtrack(r, c, idx):
            if idx == len(word):
                return True

            if r < 0 or r >= m or c < 0 or c >= n or board[r][c] != word[idx]:
                return False

            temp = board[r][c]
            board[r][c] = '#'

            found = (backtrack(r + 1, c, idx + 1)
                  or backtrack(r - 1, c, idx + 1)
                  or backtrack(r, c + 1, idx + 1)
                  or backtrack(r, c - 1, idx + 1))

            board[r][c] = temp
            return found

        for i in range(m):
            for j in range(n):
                if backtrack(i, j, 0):
                    return True

        return False
Go 1.26
func exist(board [][]byte, word string) bool {
    m, n := len(board), len(board[0])

    boardCount := make(map[byte]int)
    for i := 0; i < m; i++ {
        for j := 0; j < n; j++ {
            boardCount[board[i][j]]++
        }
    }

    wordCount := make(map[byte]int)
    for i := 0; i < len(word); i++ {
        wordCount[word[i]]++
    }

    for c, cnt := range wordCount {
        if boardCount[c] < cnt {
            return false
        }
    }

    w := []byte(word)
    if boardCount[w[0]] > boardCount[w[len(w)-1]] {
        for i, j := 0, len(w)-1; i < j; i, j = i+1, j-1 {
            w[i], w[j] = w[j], w[i]
        }
    }

    var backtrack func(r, c, idx int) bool
    backtrack = func(r, c, idx int) bool {
        if idx == len(w) {
            return true
        }

        if r < 0 || r >= m || c < 0 || c >= n || board[r][c] != w[idx] {
            return false
        }

        temp := board[r][c]
        board[r][c] = '#'

        found := backtrack(r+1, c, idx+1) ||
            backtrack(r-1, c, idx+1) ||
            backtrack(r, c+1, idx+1) ||
            backtrack(r, c-1, idx+1)

        board[r][c] = temp
        return found
    }

    for i := 0; i < m; i++ {
        for j := 0; j < n; j++ {
            if backtrack(i, j, 0) {
                return true
            }
        }
    }

    return false
}

Frequently asked questions

What is the optimal time complexity of Word Search?

The most efficient approach in this guide ("Backtracking with Pruning") runs in O(m * n * 3^L) worst case, but typically much faster due to pruning time and uses O(L) for the recursion stack depth extra space. Earlier brute-force approaches are slower; see all 2 approaches above for the full progression.

What pattern does Word Search use?

Word Search is a medium-level Backtracking problem involving Array, String, Backtracking. Recognizing the pattern is half the work — see the intuition section above for how to spot it.

Can Word Search be solved without extra space?

The most space-efficient approach in this guide uses O(L) for the recursion stack depth extra space (excluding the input). If you're aiming for in-place — see the trade-off in the algorithm section above — the brute-force approach uses O(L) for the recursion stack depth, while the optimized version uses O(L) for the recursion stack depth.

Which programming languages does this Ratta solution support?

Every approach above ships with verified, runnable solutions in C++, Java, Python, and Go. The starter templates in the workspace match the same four languages so you can practice in your interview language of choice.

Original problem: leetcode.com

Loading editor...
board=
[["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]]
word=
"ABCCED"
true