Skip to main content

Someone wanted to learn this too, so Grasp built them a personal learning path.

You can keep exploring this one or create your own

Optimizing Word Search with Tries and Backtracking

Welcome to the final lesson of our module on advanced interview patterns. In our previous lesson, we explored how bitmasks can elegantly represent and enumerate subsets, a powerful technique for certain combinatorial problems. Today, we'll tackle a different kind of combinatorial challenge that requires combining a sophisticated data structure with a classic search algorithm.

We'll be diving into the Word Search II problem, often seen in interviews. It's a fantastic problem because it appears simple on the surface but has a performance bottleneck that requires a clever insight to solve efficiently. Your goal for this lesson is to learn how to combine a Trie data structure with backtracking search to dramatically prune the search space and solve this problem optimally. This is a culminating skill that brings together concepts from several previous modules.

The Problem: Finding Words on a Board

The problem is straightforward to state: given a 2D grid of letters, like a Boggle board, and a dictionary of words, find all the words from the dictionary that can be formed by traversing adjacent cells (horizontally or vertically) without reusing a cell in the same word.

A 4x4 board of letters. For a given dictionary, we might be asked to find words like "oath" (highlighted in orange and red) but not "eat" if it required reusing the 'a' at `(0,1)`. We could, however, find "eat" using the letters at `(1,0)`, `(1,1)`, `(1,2)`.

The Brute-Force Backtracking Approach

Given your experience with backtracking from Module 6, your first instinct might be to iterate through each word in the dictionary. For each word, you would then perform a separate backtracking search across the entire board to see if that specific word exists.

This approach is correct, but it's very inefficient, especially if the dictionary is large. You would be re-traversing the same paths on the board over and over again for words that share common prefixes (e.g., "oath", "oat", "oars").

The following video segment from NeetCode explains this naive approach and its significant performance drawbacks.

Word Search II - Backtracking Trie - Leetcode 212 - Python

Watch the introduction to the problem and the analysis of the brute-force approach.

Watch from the beginning until this explanation of the inefficient time complexity. The key takeaway is understanding why multiplying the search effort by the number of words is the bottleneck we need to eliminate.

The Key Insight: Pruning with a Trie

The inefficiency comes from searching for each word independently. The solution is to search for all words at once. How can we do that? By using a data structure that organizes the dictionary based on shared prefixes: the Trie.

We've worked with Tries before in this module. Here, its power becomes fully apparent. We can build a single Trie containing all the words from our dictionary. Then, we perform a single backtracking search (DFS) over the board. As we build a path of characters on the board, we simultaneously traverse the Trie.

The magic happens here: if at any point our path on the board (e.g., "o-a-x") does not correspond to a valid prefix in our Trie, we know that no word in our dictionary can possibly be formed by continuing down this path. We can immediately prune this entire branch of the search, saving an enormous amount of computation.

The following resources provide an excellent intuitive walkthrough of this core idea.

212. Word Search II - In-Depth Explanation

The "Intuition" section of this Algo.monster article clearly explains why a Trie is the perfect tool for this problem.

Read the section titled Intuition. Focus on the idea that many words might share common prefixes and how a Trie allows us to check them all simultaneously.

Word Search II - Backtracking Trie - Leetcode 212 - Python

NeetCode's video continues with a fantastic visualization of how the Trie and the board search work together.

Watch the segment from this point. The presenter does a great job of showing how you move through the Trie as you move through the board, and how you backtrack when a prefix doesn't exist.

The Combined Algorithm: Trie + Backtracking

Now let's formalize the complete algorithm. It elegantly weaves together the Trie traversal and the board traversal.

Word Boggle - GeeksforGeeks

This article provides a concise, step-by-step summary of the optimal algorithm.

Read the section under the heading Expected Approach. This gives you a high-level checklist of the algorithm's components.

Here is a more detailed breakdown of the process:

  1. Trie Construction:

    • Create a TrieNode structure. Each node needs an array or map for its children and a way to mark if it's the end of a complete word. Instead of just a boolean isWord, it's useful to store the actual word string at the end node. This saves us from having to rebuild the word from the path.
    • Insert every word from the dictionary into the Trie.
  2. Board Traversal:

    • Iterate through every cell (row, col) of the board.
    • For each cell, begin a backtracking depth-first search (DFS), starting from the root of the Trie.
  3. Recursive DFS Function dfs(row, col, trieNode):

    • Pruning/Base Case: Check for termination conditions:
      • The coordinates (row, col) are out of bounds.
      • The cell (row, col) has already been visited in the current path.
      • The character at board[row][col] does not correspond to a child of the current trieNode. If so, this path is invalid. return.
    • State Change & Exploration:
      • Mark the current cell (row, col) as visited (e.g., by changing its value to a special character like '#').
      • Move to the next Trie node: nextNode = trieNode.children[board[row][col]].
      • Word Found: If nextNode marks the end of a word (i.e., nextNode.word is not null), add nextNode.word to your results. To handle duplicates and improve efficiency, immediately set nextNode.word = null. This prevents the same word from being found again.
      • Recursively call dfs for all four neighbors (up, down, left, right), passing the nextNode.
    • Backtrack: After exploring all neighbors, restore the board by changing the cell (row, col) back to its original character. This is a critical step, as it allows the cell to be used in other search paths originating from different starting points.

This Algo.monster article provides a fantastic, detailed walkthrough of this entire process, complete with a TypeScript implementation.

212. Word Search II - In-Depth Explanation

This resource breaks down the algorithm, walks through an example, and provides the full code.

First, read the Solution Approach section for a breakdown of the Trie, DFS, and backtracking mechanism. Then, follow the Example Walkthrough to trace the algorithm's execution on a small example. Finally, study the TypeScript implementation provided under the heading Solution Implementation. This is a clean, interview-ready implementation of the algorithm we've discussed.

Common Pitfalls

This is a complex algorithm with several places to make a mistake. Being aware of them is half the battle.

  • Incorrect Backtracking: Forgetting to restore the character on the board after exploring a path is a critical error. It effectively removes that character from consideration for all subsequent searches.
  • Handling Duplicates: If a word can be formed in multiple ways, you might add it to your results multiple times. Using a Set to store results is an easy fix. The better fix, as mentioned above, is to "de-register" the word in the Trie once it's found (node.word = null). This not only prevents duplicates but also prunes the search, as the algorithm won't bother continuing down a path if it has already found the word at that node.

The Algo.monster article has an excellent section on these very points.

212. Word Search II - In-Depth Explanation

Reviewing common mistakes helps solidify your understanding of the algorithm's critical components.

Read the section on Common Pitfalls, paying close attention to the explanations for "Not Handling Duplicate Words" and "Incorrect Backtracking".

Conclusion

You've now learned one of the most classic and powerful combinations in algorithm interviews: using a Trie to supercharge a backtracking search. This pattern is a prime example of how choosing the right data structure can turn an intractable brute-force solution into an efficient and elegant one.

Here are the key takeaways from this lesson:

  • The Bottleneck: A naive word search that iterates through the dictionary is too slow because it re-explores common prefixes on the board repeatedly.
  • The Solution: Build a Trie from the dictionary. This allows you to search for all words simultaneously.
  • The Synergy: Combine a standard backtracking DFS on the board with a simultaneous traversal of the Trie.
  • The Pruning: If a path on the board doesn't exist as a prefix in the Trie, you can immediately abandon that search path.
  • Critical Details: Correctly implementing the backtracking step (restoring the board state) and handling found words (using a result set or modifying the Trie) are essential for a correct and optimal solution.

This lesson concludes our module on advanced patterns. You are now equipped with a powerful set of tools, from bitmasks to Tries with backtracking. In our final module, Mixed-Pattern Interview Practice, we will focus on the skill of problem identification. You'll be presented with unseen problems and challenged to identify which of the many patterns you've learned is the right one to apply, without being told beforehand. This is the final step in preparing you for real-world interview scenarios.

Can't find a good explanation? Sign up and we'll make it for you

Sign up