Skip to content

Conversation

@pertrai1
Copy link
Owner

@pertrai1 pertrai1 commented Dec 17, 2025

Summary by CodeRabbit

  • New Features

    • Added Word Search II (Problem 0212) to the LeetCode Hard problems collection (163 total solved).
  • Documentation

    • Added comprehensive problem description with examples and constraints.
    • Introduced structured quickstart guide and post-mortem template for problem-solving and analysis.

✏️ Tip: You can customize this high-level summary in your review settings.

@pertrai1 pertrai1 requested a review from Copilot December 17, 2025 18:49
@pertrai1 pertrai1 added the code challenge this is a coding challenge label Dec 17, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 17, 2025

Walkthrough

This PR introduces a new LeetCode Hard problem (0212 - Word Search II) with implementation, accompanying documentation, and templates. The main repository README is updated to reflect the addition, incrementing hard problems from 12 to 13 and total solved problems from 162 to 163. Minor code formatting adjustments are applied to the solution file.

Changes

Cohort / File(s) Summary
Documentation & Problem Setup
leetcode/hard/0212-word-search-ii/README.md, leetcode/hard/0212-word-search-ii/POST_MORTEM_TEMPLATE.md, leetcode/hard/0212-word-search-ii/word-search-ii-quickstart.md
Added complete problem description with examples and constraints; introduced post-mortem template with structured sections for reflection and analysis; created Polya-inspired quickstart guide covering Understand, Plan, Execute, and Reflect phases with concrete templates.
Repository Overview
README.md
Updated LeetCode problems count from 162 to 163; incremented Hard Problems solved from 12 to 13; added new "Backtracking & Trie" subsection under Hard Problems with entry for 0212 - Word Search II.
Implementation
leetcode/hard/0212-word-search-ii/word-search-ii.ts
Standardized string literal formatting (e.g., "#" to '#'); reformatted DFS directions array to multiple lines; expanded boundary checks while preserving functional equivalence.
Cleanup
0212-word-search-ii/README.md
Removed entire file content (previously in root directory).

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

  • Main README update: Verify count increments and subsection formatting are consistent with existing structure
  • Code formatting in word-search-ii.ts: Confirm style changes don't affect functionality or introduce unintended modifications

Poem

🐰 A new challenge hops into the warren,
Word Search II, a trie-backed darling so fair,
Backtracking through mazes with care,
Templates and guides light the way,
Documentation squared—a hop-worthy day! 🌟

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding a new LeetCode problem (Word Search II) to the repository, as evidenced by new files and updates to the main README tracking problem count.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch leetcode-12172025

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link

@codex review

1 similar comment
@github-actions
Copy link

@codex review

@github-actions
Copy link

github-actions bot commented Dec 17, 2025

Claude finished @pertrai1's task in 2m 5s —— View job


Code Review Complete ✅

Great work on this challenging problem! This is a well-structured solution to a hard-level LeetCode problem. Below is a detailed review following the learning-focused guidelines.


1. Correctness ✅

Verdict: The solution is correct and handles all requirements properly.

Edge Cases Handled:

  • ✅ Empty board or empty words list (line 50-52)
  • ✅ No matching words (returns empty array)
  • ✅ Duplicate words (handled by Set + nulling node.word at line 79)
  • ✅ Same cell not reused within a word (backtracking at lines 83, 108)
  • ✅ All 4 directional neighbors checked (lines 85-90)

Logic Verification:
The solution correctly implements the two-phase approach:

  1. Build Trie from all words (lines 57-60)
  2. DFS from each cell that matches a Trie prefix (lines 63-70)

2. Complexity Analysis

Time Complexity: O(M × N × 4^L)

  • M × N: Iterate through every cell on the board
  • 4^L: For each cell, DFS explores up to 4 directions recursively, where L is the maximum word length (constrained to ≤ 10)
  • Building Trie: O(K × W) where K is the number of words and W is average word length, but this is dominated by the DFS

Space Complexity: O(K × W + L)

  • O(K × W): Trie storage for K words with average length W
  • O(L): Recursion stack depth (max word length = 10)
  • Note: The board is modified in-place, so no extra space for visited tracking

Your post-mortem correctly identifies this as an optimal solution! The Trie dramatically reduces the search space compared to checking each word independently.


3. Algorithmic Pattern & Key Insight

Patterns Used:

  1. Trie (Prefix Tree) - For efficient prefix matching across multiple words
  2. Backtracking DFS - To explore all possible paths on the grid
  3. In-place Modification - Using the board itself as visited tracker

Why This Pattern Works:

This problem combines two classic patterns because:

  • Trie allows you to check if a path is a valid prefix in O(1), pruning invalid branches early
  • Backtracking lets you explore all paths while maintaining the constraint that cells can't be reused

The Key Insight:

Instead of searching for each word independently (which would be O(K × M × N × 4^L)), build a Trie and search once. The Trie enables simultaneous searching for all words by checking prefixes. When you traverse "ea" on the board, you're simultaneously checking if it leads to "eat", "ear", "easy", etc.

The brilliance: Setting node.word = null at line 79 prevents finding the same word multiple times from different paths, while still allowing the Trie structure to guide searches for other words sharing that prefix.


4. Code Quality

Strengths:

✅ Clear variable names (wordsFound, backtrackDFS, directions)
✅ Well-commented logic explaining each step
✅ Good separation of concerns (Trie class, main function, DFS helper)
Improved formatting in this PR - the multi-line directions array and conditional checks are much more readable
✅ Proper use of const and let
✅ TypeScript types properly defined

Minor Suggestions:

1. Trie Optimization (Advanced):
The Trie class includes search() and startsWith() methods (lines 25-46) that aren't used in this solution. These are standard Trie methods, which is fine for learning/reusability, but for a more focused solution, they could be removed.

2. Trie Pruning (Optimization):
You could add Trie pruning to remove branches after finding all words with that prefix. Add this after line 79:

// Prune the Trie to optimize further searches
if (Object.keys(node.children).length === 0) {
  // Remove this node from parent (would need parent reference)
}

This is an advanced optimization that provides marginal improvement in practice.


5. Common Mistakes to Avoid

You avoided several common pitfalls with this problem:

Not using a Trie: Some attempt to DFS for each word separately → O(K × M × N × 4^L) which TLEs
Forgetting to backtrack: You correctly restore board[i][j] = temp at line 108
Finding duplicates: Handled by using a Set AND nulling node.word
Not pruning early: You check trie.root.children[char] before starting DFS (line 67)

One subtle point: Your check at line 67 (if (trie.root.children[char])) is an excellent optimization that prevents unnecessary DFS calls!


6. Alternative Approaches

Current Approach: Trie + DFS Backtracking

  • Time: O(M × N × 4^L)
  • Space: O(K × W)
  • Best for: Multiple words, shared prefixes (this problem!)

Alternative: Individual DFS per Word

  • Time: O(K × M × N × 4^L)
  • Space: O(L) for recursion stack
  • Best for: Very few words (K < 5) or no shared prefixes
  • Trade-off: Much simpler code but won't pass LeetCode time limits for this problem

7. Related Problems for Practice

These problems use similar patterns:

  1. LeetCode 79 - Word Search (Medium)

    • Same backtracking DFS pattern, but for a single word
    • Great warm-up before this problem!
  2. LeetCode 208 - Implement Trie (Prefix Tree) (Medium)

    • Pure Trie implementation practice
    • You already have this implemented!
  3. LeetCode 211 - Design Add and Search Words Data Structure (Medium)

    • Trie with wildcard matching using DFS
    • Combines both concepts
  4. LeetCode 1219 - Path with Maximum Gold (Medium)

    • Similar backtracking DFS on a grid pattern

8. Documentation Quality

README.md: ✅ Excellent - includes problem description, examples, constraints, and difficulty badge

POST_MORTEM_TEMPLATE.md: ✅ Thorough self-reflection showing growth mindset

  • You correctly identified Trie + Backtracking as the key patterns
  • Honest about initially missing the backtracking aspect
  • Good self-rating (3s show room for growth, 4 in communication is deserved!)

word-search-ii-quickstart.md: ✅ Outstanding Pólya-style problem-solving guide

  • Well-structured approach (Understand → Plan → Execute → Reflect)
  • Shows systematic thinking

Learning Summary

Core Concepts Mastered:

  1. Trie Data Structure - Efficient prefix matching for multiple strings
  2. Backtracking DFS - Exploring all paths with undo capability
  3. In-place State Management - Using board cells as visited markers
  4. Early Pruning - Checking Trie children before DFS

The "Aha!" Moment:

The power of this solution comes from prefix sharing. When you DFS "o" → "a" → "t" on the board, you're simultaneously checking if it leads to "oath", "oat", "oats", etc. A naive approach would search for each word separately, repeating work.

Pattern Recognition:

Trie + Backtracking appears when you need to:

  • Search for multiple strings with shared prefixes
  • Explore constrained paths (can't revisit cells)
  • Prune invalid branches early

Interview Tip:

In an interview, always ask: "How many words are we searching for?" If the answer is "potentially thousands with shared prefixes", immediately think Trie!


Overall Assessment: This is a strong solution to a challenging problem. The code is clean, well-documented, and demonstrates solid understanding of both Trie and backtracking patterns. The formatting improvements in this PR enhance readability. Keep up the excellent work! 🎯


@chatgpt-codex-connector
Copy link

To use Codex here, create a Codex account and connect to github.

1 similar comment
@chatgpt-codex-connector
Copy link

To use Codex here, create a Codex account and connect to github.

@github-actions
Copy link

🧮 Solution Analysis

PR: LeetCode Word Search II Problem
Files Changed: leetcode/hard/0212-word-search-ii/word-search-ii.ts

📊 Code Quality Results

ESLint Analysis

  • Errors: 0
  • Warnings: 1

Issues by file:

  • /home/runner/work/coding-challenges/coding-challenges/leetcode/hard/0212-word-search-ii/word-search-ii.ts: 0 errors, 1 warnings

🔍 Complexity Analysis

💡 Analysis Tips

  • Time Complexity: Focus on the dominant operation in loops
  • Space Complexity: Consider auxiliary data structures
  • Optimization: Look for opportunities to improve efficiency
  • Edge Cases: Ensure your solution handles empty inputs, null/undefined, edge conditions

📁 Analysis Files Created

Detailed analysis files have been automatically generated and committed to docs/analysis/ for future reference.

🎯 Next Steps

  1. Review any ESLint errors/warnings above
  2. Consider the complexity analysis - is this the optimal approach?
  3. Test with edge cases mentioned in the problem description
  4. Add comments explaining complex logic
  5. Check the docs/analysis/ directory for detailed complexity analysis files

Analysis generated for PR files only

1 similar comment
@github-actions
Copy link

🧮 Solution Analysis

PR: LeetCode Word Search II Problem
Files Changed: leetcode/hard/0212-word-search-ii/word-search-ii.ts

📊 Code Quality Results

ESLint Analysis

  • Errors: 0
  • Warnings: 1

Issues by file:

  • /home/runner/work/coding-challenges/coding-challenges/leetcode/hard/0212-word-search-ii/word-search-ii.ts: 0 errors, 1 warnings

🔍 Complexity Analysis

💡 Analysis Tips

  • Time Complexity: Focus on the dominant operation in loops
  • Space Complexity: Consider auxiliary data structures
  • Optimization: Look for opportunities to improve efficiency
  • Edge Cases: Ensure your solution handles empty inputs, null/undefined, edge conditions

📁 Analysis Files Created

Detailed analysis files have been automatically generated and committed to docs/analysis/ for future reference.

🎯 Next Steps

  1. Review any ESLint errors/warnings above
  2. Consider the complexity analysis - is this the optimal approach?
  3. Test with edge cases mentioned in the problem description
  4. Add comments explaining complex logic
  5. Check the docs/analysis/ directory for detailed complexity analysis files

Analysis generated for PR files only

@github-actions
Copy link

✅ Test Results

Tests have been executed for this pull request.

📊 Coverage Summary

Metric Coverage
Statements 93.63%
Branches 87.61%
Functions 82.14%
Lines 93.39%

Check the workflow logs for detailed test output.

@github-actions
Copy link

Here's a detailed review of your LeetCode solution for "Word Search II", following the educational guidelines:


1. Pattern Identification

This solution primarily uses two algorithmic patterns:

  • Trie (Prefix Tree): To efficiently store all the words and enable quick prefix matching during the search. This is crucial for optimizing the search for multiple words that might share common prefixes.
  • Backtracking (Depth-First Search - DFS): To explore all possible paths on the board starting from each cell. The DFS explores a path, marks cells as visited, and if a path doesn't lead to a valid word or prefix, it "backtracks" by unmarking the cells and trying other directions.

2. Complexity Verification

Let M be the number of rows, N be the number of columns in the board.
Let N_W be the number of words in the words list.
Let L_max be the maximum length of a word in words.

  • Time Complexity:

    • Stated: Not explicitly stated, but the POST_MORTEM_TEMPLATE.md suggests O(M * N * 3^L_max + N_W * L_max).
    • Verification: This stated complexity is generally accurate for this problem and approach.
      • Trie Construction: Building the Trie takes O(N_W * L_max) time. In the worst case, each character of each word needs to be inserted, creating a new Trie node.
      • DFS Traversal:
        • We initiate a DFS from each of the M * N cells on the board.
        • Each DFS call explores paths. The maximum depth of a path is L_max.
        • At each step of the DFS, we can move in up to 4 directions. However, since we cannot go back to the immediately previous cell, the effective branching factor is reduced to at most 3 after the first step (e.g., if you came from 'left', you can go 'up', 'down', 'right').
        • The Trie acts as a powerful pruning mechanism: if the current character doesn't form a valid prefix of any word in the Trie, the DFS branch is immediately terminated.
        • The optimization node.word = null after finding a word ensures that we don't add the same word multiple times and potentially prunes further searches that would only lead to that specific word again.
        • The total time for the DFS part is approximately O(M * N * 3^L_max).
    • Correct Complexity: O(N_W * L_max + M * N * 3^L_max).
  • Space Complexity:

    • Stated: Not explicitly stated, but the POST_MORTEM_TEMPLATE.md suggests O(N_W * L_max) for the Trie.
    • Verification: This is largely accurate.
      • Trie: In the worst case, if all words share minimal prefixes, the Trie could store up to N_W * L_max characters (and thus nodes). So, O(N_W * L_max).
      • wordsFound Set: Stores the unique words found. In the worst case, all N_W words are found, each with length up to L_max. This contributes O(N_W * L_max) space.
      • Recursion Stack: The maximum depth of the DFS recursion is L_max (the maximum length of a word). So, O(L_max).
    • Correct Complexity: O(N_W * L_max). The L_max for the recursion stack is usually dominated by N_W * L_max for the Trie and wordsFound set.

3. Key Insight

The key insight for this problem is the synergistic combination of a Trie (Prefix Tree) with Depth-First Search (DFS) and Backtracking.

  1. The "Aha!" Moment: A naive approach would be to iterate through each word and for each word, perform a separate DFS on the board. This would be incredibly inefficient, especially with a large number of words (N_W up to 3 * 10^4). The crucial realization is that many words share common prefixes. Instead of restarting the search for each word, we can use a Trie to store all words. As we traverse the board, we simultaneously traverse the Trie.

  2. How it works:

    • Trie for Pruning: By building a Trie of all target words, we can quickly check if the characters we've collected so far form a valid prefix of any word we're looking for. If at any point the path on the board doesn't match a prefix in the Trie, we can immediately stop exploring that path (pruning), saving enormous amounts of computation.
    • Single DFS for Multiple Words: Instead of N_W separate DFS calls, we perform M * N DFS calls (one from each starting cell). Each of these DFS calls tries to find any word that can be formed from that starting point.
    • Optimization for Duplicates: Once a word is found (node.word is not null), it's added to the wordsFound set. Crucially, setting node.word = null after adding it to wordsFound prevents the same word from being found again through a different path on the board, and it also helps prune further exploration if that specific word is no longer needed.

4. Edge Case Analysis

The current solution handles several edge cases correctly:

  • Empty board or words list: The initial if (board.length < 1 || words.length < 1) check correctly returns an empty array.
  • No words found: If no words match, wordsFound will remain empty, and an empty array will be returned.
  • Single cell board: Handled correctly by the loops and DFS logic.
  • Words longer than possible paths: The Trie pruning and DFS depth limit (L_max) naturally handle this, as paths that exceed L_max or don't form valid prefixes will be pruned.
  • Words with repeated characters: The board[i][j] = "#" mechanism correctly prevents reusing the same physical cell in a single word path.
  • Words sharing prefixes (e.g., "apple", "app"): Both words will be found if present on the board. The Trie structure naturally accommodates this.
  • Words that are prefixes of other words (e.g., "cat", "cats"): If "cat" is found, it's added. The DFS continues, and if "cats" is also found, it will be added as well. The node.word = null optimization is key here, as it ensures "cat" is only added once but doesn't prevent "cats" from being found if it's a longer word using the same prefix.

5. Learning Points

  • Similar Problems using this pattern:

  • Common Mistakes with this pattern:

    • Time Limit Exceeded (TLE): The most common mistake is not using a Trie, leading to N_W separate DFS calls, which is too slow.
    • Incorrect Backtracking: Failing to correctly mark cells as visited before a recursive call and unmark them after the call (the board[i][j] = "#"; and board[i][j] = temp; steps are crucial). This can lead to incorrect results (reusing cells) or infinite loops.
    • Handling Duplicates: Not using a Set to store wordsFound can lead to duplicate words in the result if they can be formed via different paths.
    • Inefficient Trie Pruning: Not setting node.word = null after a word is found can lead to redundant work, as the algorithm might keep finding and adding the same word through different paths, even if the Set handles deduplication. Nullifying the word property also helps prune subsequent searches that would only lead to that specific word.
  • Variations of this problem:

    • Allowing diagonal movements.
    • Finding words that match a regular expression (more advanced Trie structures like Aho-Corasick or NFA simulation might be needed).
    • Finding the longest word, or the word with the highest score (if letters have values).
    • Finding paths for words where cells can be reused (e.g., in a Boggle-like game, but usually with a constraint on consecutive reuse or limited reuse).

6. Code Quality

The code is generally well-structured and follows good practices for competitive programming.

  • Variable Naming:
    • TrieNode, Trie, findWords, board, words, wordsFound, trie, root, children, word, current, char, i, j, ni, nj, dx, dy, temp, node are all descriptive and clear.
  • Code Structure:
    • The separation into TrieNode and Trie classes is excellent and promotes reusability.
    • The findWords function clearly orchestrates the Trie construction and the DFS traversal.
    • The backtrackDFS is a well-encapsulated recursive helper function.
  • Readability:
    • The logic is straightforward, making it easy to follow the flow of the algorithm.
    • The use of const for directions and temp is good.
    • The Array.from(wordsFound) at the end is a clean way to convert the Set to an array.
  • TypeScript Best Practices:
    • Type annotations (string[][], string[], string, boolean, TrieNode) are used correctly, enhancing code clarity and maintainability.
    • Modern ES6+ syntax (class, arrow functions, for...of) is used.
    • Strict equality (===) is used implicitly in object property access and other comparisons.

Minor Improvement Suggestions:

  • Comments: While the code is relatively clear, a few strategic comments could further explain why certain steps are taken, especially for the node.word = null optimization. For example:
    if (node.word) {
      wordsFound.add(node.word);
      node.word = null; // Mark word as found and remove it from Trie to avoid duplicates and redundant searches
    }
  • Magic Number/Character: The "#" character used to mark visited cells is a "magic" character. While common in competitive programming, in a production setting, one might prefer a separate visited 2D boolean array to avoid modifying the input board or to use a more explicit state. However, for LeetCode, modifying the board temporarily is a standard and efficient approach.

7. Alternative Approaches

  1. Brute-Force DFS for Each Word (Less Optimal):

    • Approach: For each word in the words list, iterate through every cell (r, c) on the board. If board[r][c] matches the first character of the current word, initiate a DFS from (r, c) to try and find the rest of that specific word.
    • Trade-offs:
      • Pros: Simpler to conceptualize and implement if you're only familiar with basic DFS.
      • Cons: Extremely inefficient. Time Complexity: O(N_W * M * N * 3^L_max). This will almost certainly result in a Time Limit Exceeded (TLE) error for the given constraints, as it repeatedly searches for words even if they share long prefixes.
  2. BFS with Trie (More Complex for this problem):

    • Approach: While DFS is generally preferred for pathfinding and exploring combinations in grids, a BFS approach could also be considered. You would start a BFS from each cell, and the state in your queue would need to track (row, col, currentTrieNode, pathString, visitedCells).
    • Trade-offs:
      • Pros: Guarantees finding the shortest path first (not relevant here since word length is fixed by the word itself). Might be less prone to stack overflow for very deep paths (though L_max is small here).
      • Cons: Managing the visitedCells (which would need to be unique for each path in the BFS queue) and the currentTrieNode for each state in the queue would be significantly more complex than with DFS's implicit stack and simpler backtracking. The memory footprint for the queue could also be larger.

The chosen approach (Trie + DFS with backtracking) is indeed the most optimal and common solution for this problem.

@github-actions
Copy link

Here's a detailed review of your Word Search II solution, following the specified guidelines:


1. Pattern Identification

This solution effectively combines two fundamental algorithmic patterns:

  • Trie (Prefix Tree): Used to store the dictionary of words efficiently. It allows for quick checking of whether a given character sequence forms a valid prefix of any word in the dictionary, which is crucial for pruning the search space.
  • Backtracking (Depth-First Search - DFS): Employed to explore all possible paths on the board starting from each cell. The DFS recursively explores adjacent cells, building up a character sequence. It "backtracks" by undoing its choices (unmarking visited cells) to explore alternative paths.

This combination is a classic approach for problems involving searching for multiple patterns (words) on a grid or graph structure.

2. Complexity Verification

Let M be the number of rows, N be the number of columns in the board.
Let W_count be the number of words in the words list.
Let W_max be the maximum length of a word in words.
Let L_total be the total number of characters in all words (i.e., sum(word.length for word in words)).

  • Time Complexity:

    • Stated: O(M * N * 4^L + W) where L is max word length, W is total characters in words.
    • Correct Complexity: O(M * N * 3^W_max + L_total)
    • Why:
      1. Trie Construction: Building the Trie takes O(L_total) time, as each character of every word is inserted once.
      2. DFS Traversal:
        • There are M * N possible starting cells on the board.
        • From each starting cell, a DFS is performed.
        • The depth of the DFS recursion is at most W_max (the maximum length of a word).
        • At each step of the DFS, we explore up to 4 adjacent cells. However, since we cannot revisit the immediately previous cell (due to marking cells as visited), the effective branching factor is closer to 3 for subsequent steps. So, 3^W_max is a more accurate estimate for the number of paths explored from a single starting cell in the worst case (if all paths lead to valid prefixes).
        • The critical optimization is the Trie: if the current character sequence does not form a prefix of any word in the Trie (node.children[board[ni][nj]] is undefined), the DFS branch is immediately pruned. This significantly reduces the actual paths explored compared to a naive DFS.
        • Therefore, the total time for the DFS part is approximately M * N * 3^W_max.
      • Combining these, the overall time complexity is O(M * N * 3^W_max + L_total). The stated 4^L is a common upper bound, but 3^L is slightly more precise for grid DFS. Given the constraints (M, N <= 12, W_max <= 10), this complexity is efficient enough.
  • Space Complexity:

    • Stated: O(L + M * N) where L is total length of words, M*N for recursion stack.
    • Correct Complexity: O(L_total + W_max)
    • Why:
      1. Trie: The Trie stores all words. In the worst case (no shared prefixes), it stores all characters, leading to O(L_total) space.
      2. wordsFound Set: Stores the unique words found. In the worst case, all W_count words are found, each with length up to W_max. This contributes O(L_total) space in total for storing the strings.
      3. Recursion Stack: The maximum depth of the DFS recursion stack is W_max (the length of the longest word being searched). This contributes O(W_max) space.
      4. Board Modification: The board is modified in-place to mark visited cells, so it doesn't add extra space.
      • Combining these, the overall space complexity is O(L_total + W_max). The M*N in the stated complexity for recursion stack is inaccurate; the recursion depth is bounded by word length, not board size, because cells are marked visited.

3. Key Insight

The key insight for this problem is to transform the problem from "search for each word individually on the board" into "search for all words simultaneously using shared prefixes."

  1. Trie for Collective Prefix Search: Instead of performing a separate DFS for each word in the words list (which would be W_count times slower), we build a Trie from all the words. This allows us to represent all possible word prefixes in a single data structure.
  2. DFS with Trie Pruning: When traversing the board using DFS, at each step, we check if the character we are currently on forms a valid prefix with the characters visited so far by querying the Trie. If the current path does not match any prefix in the Trie, we immediately prune that DFS branch. This drastically reduces the number of unnecessary paths explored, making the solution feasible within time limits.
  3. In-place Visited Marking and Backtracking: The use of board[i][j] = "#" to mark a cell as visited during a path exploration, and then restoring it (board[i][j] = temp) during backtracking, is essential. This ensures that a cell is not reused within the same word's path and correctly resets the board state for exploring other paths.
  4. Trie Node Word Flag and Set for Results: Storing the full word in the TrieNode (via node.word = word) and then setting it to undefined after adding it to the wordsFound set is a clever optimization. It prevents the same word from being added multiple times if it can be found through different paths on the board, and more importantly, it effectively removes the word from the Trie for future searches, potentially pruning further branches that would only lead to this already-found word. The wordsFound Set then naturally handles any duplicates that might arise from different starting points leading to the same word.

4. Edge Case Analysis

The solution handles several important edge cases correctly:

  • Empty Board or Empty Words List: The initial check if (board.length < 1 || words.length < 1) correctly returns an empty array.
  • No Words Found: If no words from the list are present on the board, the wordsFound set will remain empty, and Array.from(wordsFound) will correctly return [].
  • Single-Character Words: Handled correctly, as the DFS can match a single character at a Trie node that marks a complete word.
  • Words Longer Than Board Dimensions: Implicitly handled. A word of length L requires at least L unique cells. If L > M * N, it's impossible to form, and the DFS will naturally exhaust all valid paths before reaching that length.
  • Board with Only One Cell: Handled. The DFS will correctly check the single cell and its non-existent neighbors.
  • Words with Common Prefixes: This is precisely what the Trie is designed for, so it's handled efficiently.
  • Duplicate Words in Input: The problem statement explicitly says "All the strings of words are unique." However, if this constraint were relaxed, the Trie would still store each unique word once, and the wordsFound Set would ensure unique output.
  • Same Word Found via Different Paths/Starting Points: The wordsFound Set ensures that each unique word is added to the result only once, regardless of how many times or from how many starting points it can be formed on the board. The node.word = undefined optimization further prevents re-adding from the same Trie node.

5. Learning Points

  • Similar Problems:
  • Common Mistakes with this Pattern:
    • Time Limit Exceeded (TLE): The most common mistake is not using a Trie, leading to a separate search for each word. Or, not pruning the DFS tree effectively.
    • Incorrect Backtracking: Forgetting to mark cells as visited (board[i][j] = "#") or, crucially, forgetting to unmark them (board[i][j] = temp) after a DFS branch completes. This leads to incorrect paths or TLE due to infinite loops.
    • Boundary Checks: Off-by-one errors or forgetting to check ni >= 0 && ni < board.length && nj >= 0 && nj < board[0].length in the DFS.
    • Duplicate Results: Not using a Set to store the found words, leading to duplicate entries if the same word can be formed via multiple paths.
    • Trie Implementation Bugs: Issues in insert or search logic within the Trie, especially handling the isEndOfWord (or word property here) correctly.
  • Variations of this Problem:
    • Finding the longest word on the board.
    • Finding all words on the board (not just from a given list).
    • Allowing diagonal movements in addition to horizontal/vertical.
    • Word search with wildcards in the search pattern.
    • Finding the path for a found word.

6. Code Quality

The code quality is generally very good, demonstrating clear structure and good practices.

  • Variable Naming: Names like TrieNode, Trie, board, words, wordsFound are clear and descriptive. i, j, ni, nj, dx, dy are standard and acceptable for grid traversal. temp for storing the original character is okay, though originalChar could be slightly more descriptive.
  • Code Structure:
    • The Trie class is well-defined and encapsulates its logic effectively.
    • The findWords function orchestrates the main logic, separating Trie construction from the board traversal.
    • The backtrackDFS is implemented as an inner function, which is a good pattern in JavaScript/TypeScript for easily accessing variables from the outer scope (board, wordsFound, trie).
  • Readability:
    • Comments for TrieNode properties are helpful.
    • The directions array is clear.
    • The conditional checks in backtrackDFS are well-structured.
  • Minor Improvements:
    • TrieNode Initialization: You can initialize children directly in the TrieNode class definition:
      class TrieNode {
        children: { [key: string]: TrieNode } = {}; // Initialize here
        word: string | undefined = undefined; // Also initialize
      }
    • Type Safety for current.word: In Trie.insert, current.word = word; is correct. In backtrackDFS, when you find a word, wordsFound.add(node.word) is type-safe because node.word will be a string at that point. Setting node.word = undefined afterwards is a good optimization as discussed.
    • Consistent Visited Marker: You use "#" for marking visited cells. This is a common and effective trick. Ensure this character won't appear in problem constraints for board[i][j] (which is typically lowercase English letters, so "#" is safe).
    • Implicit Return: The findWords function implicitly returns undefined if board or words are empty, which is handled by return []. This is fine.

7. Alternative Approaches

  1. Individual DFS for Each Word (Brute-Force):

    • Approach: For each word in the words list, iterate through every cell on the board. If the cell's character matches the first letter of the word, start a DFS from that cell to try and find the entire word.
    • Trade-offs:
      • Pros: Conceptually simpler to implement if you're comfortable with basic DFS. No need for a Trie.
      • Cons: Extremely inefficient. Time complexity would be roughly O(W_count * M * N * 3^W_max). Given W_count can be 3 * 10^4, this would almost certainly lead to a Time Limit Exceeded (TLE) error on LeetCode for larger inputs. This approach does not benefit from shared prefixes.
  2. BFS (Breadth-First Search):

    • Approach: Instead of DFS for exploring paths, one could use BFS. The queue would store tuples like [row, col, currentTrieNode, path] (or just [row, col, currentTrieNode] if paths are not needed, and words are tracked via Trie nodes). BFS explores paths level by level.
    • Trade-offs:
      • Pros: Guarantees finding the shortest path to a word if such a concept were relevant (not really for this problem). Can be less prone to stack overflow issues for extremely deep paths (though W_max is limited here).
      • Cons: Often more complex to manage state (like visited cells and the current string/Trie node) in a BFS queue for path-finding problems compared to the recursive nature of DFS. The overall time complexity would be similar to DFS with Trie pruning, but the constant factors might be higher due to queue operations and explicit state management.

Overall, your solution is well-designed and correctly implements the optimal approach for this challenging problem. The self-reflection in the post-mortem and quickstart guides is an excellent habit for learning and improving.

[1, 0],
[0, -1],
[-1, 0]
];

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Quality: Good formatting improvement! The multi-line array format for directions is more readable than the inline format in the previous version.

// Before: [[0, 1], [1, 0], [0, -1], [-1, 0]]
// After:
const directions = [
  [0, 1],
  [1, 0],
  [0, -1],
  [-1, 0]
];

nj >= 0 &&
nj < board[0].length &&
board[ni][nj] !== '#' &&
node.children[board[ni][nj]]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Quality: Excellent formatting improvement here too! Breaking this complex conditional across multiple lines makes it much easier to understand each check:

  • Bounds check for row (ni >= 0 && ni < board.length)
  • Bounds check for column (nj >= 0 && nj < board[0].length)
  • Visited check (board[ni][nj] !== '#')
  • Trie child check (node.children[board[ni][nj]])

This is a great example of readability over cleverness!

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a complete solution for LeetCode problem #212 "Word Search II" (Hard difficulty), including implementation, documentation, and learning materials. The solution implements a Trie data structure combined with backtracking DFS to efficiently search for multiple words on a 2D character board.

Key Changes:

  • Implements TypeScript solution using Trie + backtracking pattern with proper type annotations
  • Adds comprehensive Pólya-inspired problem-solving documentation with Understand, Plan, Execute, and Reflect phases
  • Creates detailed post-mortem analysis documenting the learning process and solution approach

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
leetcode/hard/0212-word-search-ii/word-search-ii.ts Code formatting improvements: standardizes quote style to single quotes, removes trailing whitespace, and reformats multi-line conditions for better readability
leetcode/hard/0212-word-search-ii/word-search-ii-quickstart.md New quickstart guide documenting the structured problem-solving approach with templates for each phase
leetcode/hard/0212-word-search-ii/POST_MORTEM_TEMPLATE.md New post-mortem log capturing solution exploration, mistakes, and learning takeaways
leetcode/hard/0212-word-search-ii/README.md New problem description with examples, constraints, and difficulty badge
README.md Updates problem counts (162→163 total, 12→13 hard problems) and adds new entry under "Backtracking & Trie" category
0212-word-search-ii/README.md Removes old HTML-formatted README file

Restatement:
- Given a `m x n` board and a list of words, return all the words in the list that are on the board. The words can be stitched together by connecting characters horizontally and vertically adjacent cells. The same letter cell can't be used more than once in a word.
Inputs:
- `board`: A 2D array of characterts
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: "characterts" should be "characters".

Suggested change
- `board`: A 2D array of characterts
- `board`: A 2D array of characters

Copilot uses AI. Check for mistakes.
- Restate the problem in your own words.
- Identify unknowns, givens, and constraints.
- Determine input sizes and value ranges.
- Examine edge cases (empty input, minimal/maximal inputs, duplicates, special patterns.
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing closing parenthesis at the end of the line.

Suggested change
- Examine edge cases (empty input, minimal/maximal inputs, duplicates, special patterns.
- Examine edge cases (empty input, minimal/maximal inputs, duplicates, special patterns).

Copilot uses AI. Check for mistakes.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 05eb958 and b0312d6.

📒 Files selected for processing (6)
  • 0212-word-search-ii/README.md (0 hunks)
  • README.md (3 hunks)
  • leetcode/hard/0212-word-search-ii/POST_MORTEM_TEMPLATE.md (1 hunks)
  • leetcode/hard/0212-word-search-ii/README.md (1 hunks)
  • leetcode/hard/0212-word-search-ii/word-search-ii-quickstart.md (1 hunks)
  • leetcode/hard/0212-word-search-ii/word-search-ii.ts (5 hunks)
💤 Files with no reviewable changes (1)
  • 0212-word-search-ii/README.md
🧰 Additional context used
📓 Path-based instructions (5)
**/*.md

📄 CodeRabbit inference engine (AGENTS.md)

**/*.md: Create comprehensive documentation guides for arrays, trees, graphs, heaps, and other data structures
Document reusable code templates for common algorithmic patterns
Maintain Big O complexity cheatsheets and references

Files:

  • README.md
  • leetcode/hard/0212-word-search-ii/word-search-ii-quickstart.md
  • leetcode/hard/0212-word-search-ii/POST_MORTEM_TEMPLATE.md
  • leetcode/hard/0212-word-search-ii/README.md
**/README.md

📄 CodeRabbit inference engine (CLAUDE.md)

Each problem directory should include a README.md containing: problem title and difficulty badge, link to the problem on the respective platform, problem description, examples and constraints

Files:

  • README.md
  • leetcode/hard/0212-word-search-ii/README.md
**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

**/*.ts: Use explicit type annotations for function parameters and return types in TypeScript solutions
Follow strict TypeScript configuration with ES2020 target and CommonJS modules in TypeScript solutions
Enforce full type safety in TypeScript solutions

Files:

  • leetcode/hard/0212-word-search-ii/word-search-ii.ts
**/*.{js,ts}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts}: Use clear, descriptive variable names across all solutions
Add comments for non-obvious logic or algorithm steps in code
Keep functions focused and single-purpose across all solutions
Prefer readability over cleverness in code implementation

**/*.{js,ts}: Use descriptive variable names; single letters like i, j, k are acceptable for loop iterators and standard mathematical contexts
Add comments for complex logic, but prefer self-documenting code with clear logic
Minimize external dependencies and keep solutions self-contained

**/*.{js,ts}: Use clear, descriptive variable names (not just i, j, k unless in simple loops) and add comments for non-obvious logic or algorithm steps
Keep functions focused and single-purpose, preferring readability over cleverness
Use const by default, let only when reassignment needed; avoid var except for LeetCode solution function definitions
Use modern ES6+ syntax (arrow functions, destructuring, spread operators) where appropriate
Prefer built-in methods (.map(), .filter(), .reduce()) when appropriate and use strict equality (===) over loose equality (==)

Files:

  • leetcode/hard/0212-word-search-ii/word-search-ii.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Use explicit type annotations for function parameters and return types in TypeScript
Follow strict TypeScript configuration as defined in tsconfig.json with ES2020 target and CommonJS modules

Files:

  • leetcode/hard/0212-word-search-ii/word-search-ii.ts
🧠 Learnings (4)
📓 Common learnings
Learnt from: CR
Repo: pertrai1/coding-challenges PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-26T14:46:46.097Z
Learning: Applies to [0-9]*-*/*.md : Problem README files should include: problem title with difficulty badge, problem description from LeetCode, examples with inputs/outputs, and constraints
📚 Learning: 2025-11-26T14:46:46.097Z
Learnt from: CR
Repo: pertrai1/coding-challenges PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-26T14:46:46.097Z
Learning: Applies to [0-9]*-*/*.md : Problem README files should include: problem title with difficulty badge, problem description from LeetCode, examples with inputs/outputs, and constraints

Applied to files:

  • README.md
  • leetcode/hard/0212-word-search-ii/README.md
📚 Learning: 2025-11-24T23:55:10.338Z
Learnt from: CR
Repo: pertrai1/coding-challenges PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T23:55:10.338Z
Learning: Applies to **/*.md : Document reusable code templates for common algorithmic patterns

Applied to files:

  • leetcode/hard/0212-word-search-ii/word-search-ii-quickstart.md
📚 Learning: 2025-11-26T14:46:46.097Z
Learnt from: CR
Repo: pertrai1/coding-challenges PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-26T14:46:46.097Z
Learning: Applies to [0-9]*-*/*.{js,ts} : Use standard algorithmic patterns (two pointers, sliding window, hash maps, DFS, BFS, DP) and leverage appropriate data structures (Map, Set, arrays)

Applied to files:

  • leetcode/hard/0212-word-search-ii/word-search-ii.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Agent
  • GitHub Check: review
🔇 Additional comments (8)
leetcode/hard/0212-word-search-ii/README.md (1)

1-34: LGTM! Documentation follows required structure.

The problem README includes all required elements: problem title with difficulty badge, link to LeetCode, clear problem description, examples with inputs/outputs, and comprehensive constraints. The formatting is clean and follows the repository's documentation standards.

Based on learnings, this matches the expected structure for problem README files.

README.md (3)

9-9: LGTM! Problem count updated correctly.

The total LeetCode problems count is accurately updated from 162 to 163, reflecting the addition of the new hard problem.


22-22: LGTM! Hard problems count incremented correctly.

The hard problems count is accurately updated from 12 to 13, consistent with the addition of Word Search II.


293-296: LGTM! New problem categorized appropriately.

The new "Backtracking & Trie" subsection appropriately categorizes this problem, as Word Search II is indeed a classic example combining backtracking with Trie data structures for efficient prefix matching.

leetcode/hard/0212-word-search-ii/POST_MORTEM_TEMPLATE.md (1)

1-76: Comprehensive post-mortem template.

The template provides a well-structured framework for reflection with sections covering solution exploration, mistakes, retrospective, and self-rating. The example content demonstrates proper usage of the template.

leetcode/hard/0212-word-search-ii/word-search-ii.ts (2)

1-47: LGTM! Trie implementation is correct.

The Trie data structure is properly implemented with:

  • Clear class structure with explicit type annotations
  • Correct insert, search, and startsWith methods
  • Proper use of the word marker for identifying complete words

49-112: LGTM! Efficient Trie + backtracking solution.

The implementation correctly combines Trie and backtracking for optimal word search:

  • Uses Trie for efficient prefix matching (avoiding unnecessary DFS)
  • Set prevents duplicate results
  • DFS explores all directions with proper backtracking
  • Cell marking with '#' prevents reuse within a word
  • Proper restoration of board state after backtracking

The formatting improvements (multi-line directions array, explicit boundary checks, single quotes) enhance readability.

leetcode/hard/0212-word-search-ii/word-search-ii-quickstart.md (1)

1-116: Excellent problem-solving framework.

The Polya-inspired quickstart guide provides a systematic approach to tackling coding challenges with clear phases (Understand, Plan, Execute, Reflect) and practical templates. The specific Word Search II example demonstrates effective use of the framework.

Based on learnings, this documents reusable templates for common algorithmic patterns.

### Analysis

_Explain why certain approaches worked or didn’t._

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix typo in template.

There's a typo: "characterts" should be "characters".

Apply this diff:

-  - `board`: A 2D array of characterts
+  - `board`: A 2D array of characters
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- `board`: A 2D array of characters
🤖 Prompt for AI Agents
In leetcode/hard/0212-word-search-ii/POST_MORTEM_TEMPLATE.md around line 29, fix
the typo "characterts" to "characters" by editing that word in the template so
the sentence reads with the correct spelling.

Restatement:
- Given a `m x n` board and a list of words, return all the words in the list that are on the board. The words can be stitched together by connecting characters horizontally and vertically adjacent cells. The same letter cell can't be used more than once in a word.
Inputs:
- `board`: A 2D array of characterts
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix typo in quickstart template.

There's a typo: "characterts" should be "characters".

Apply this diff:

-  - `board`: A 2D array of characterts
+  - `board`: A 2D array of characters
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- `board`: A 2D array of characterts
- `board`: A 2D array of characters
🤖 Prompt for AI Agents
In leetcode/hard/0212-word-search-ii/word-search-ii-quickstart.md around line
29, there's a typo in the quickstart template: replace "characterts" with the
correct word "characters" so the line reads "`board`: A 2D array of characters".

@pertrai1 pertrai1 merged commit 4562d51 into main Dec 17, 2025
16 of 17 checks passed
@pertrai1 pertrai1 deleted the leetcode-12172025 branch December 17, 2025 19:00
@github-actions
Copy link

📅 Spaced Repetition Reviews Scheduled!

Great job solving #0212 - Word Search Ii! 🎉

To help you retain this knowledge long-term, I've scheduled 5 review sessions using spaced repetition:

Review Days After Approximate Date
1st Review 1 day Tomorrow
2nd Review 3 days In 3 days
3rd Review 7 days In 1 week
4th Review 14 days In 2 weeks
5th Review 30 days In 1 month

What to expect:

  • GitHub issues will be automatically created for each review
  • Each issue will link back to your solution
  • You'll be reminded to solve the problem again or review your approach
  • This helps move knowledge from short-term to long-term memory!

🧠 Why Spaced Repetition?

Research shows that reviewing material at increasing intervals dramatically improves retention. This is your learning superpower!

Check docs/reviews/review-schedule.json to see your full review schedule.

github-actions bot pushed a commit that referenced this pull request Dec 22, 2025
  Problem: #72 - Edit Distance
  PR: #95
  Reviews scheduled: 5 (days 1, 3, 7, 14, 30)

  [skip ci]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

code challenge this is a coding challenge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants