HomeLeetcode212. Word Search II - Leetcode Solutions

212. Word Search II – Leetcode Solutions

Description

Given an m x n board of characters and a list of strings words, return all words on the board.

Each word must 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 word.

Examples:

Example 1:

Input: board = [["o","a","a","n"],["e","t","a","e"],["i","h","k","r"],["i","f","l","v"]], words = ["oath","pea","eat","rain"]
Output: ["eat","oath"]

Example 2:

Input: board = [["a","b"],["c","d"]], words = ["abcb"]
Output: []

Solution in Python

Python
class TrieNode:
    def __init__(self):
        self.children = {}
        self.is_end_of_word = False
        self.word = None  # Store the complete word at the end node for easy retrieval

class Trie:
    def __init__(self):
        self.root = TrieNode()
    
    def insert(self, word):
        node = self.root
        for char in word:
            if char not in node.children:
                node.children[char] = TrieNode()
            node = node.children[char]
        node.is_end_of_word = True
        node.word = word

class Solution:
    def findWords(self, board, words):
        # Build the Trie from the list of words
        trie = Trie()
        for word in words:
            trie.insert(word)
        
        # Initialize the result list
        result = []
        
        # Define the dimensions of the board
        rows = len(board)
        cols = len(board[0])
        
        # Define the directions for moving in the grid (up, down, left, right)
        directions = [(-1, 0), (1, 0), (0, -1), (0, 1)]
        
        # Define the backtracking function
        def backtrack(r, c, parent):
            letter = board[r][c]
            curr_node = parent.children[letter]
            
            # Check if we found a word
            if curr_node.is_end_of_word:
                result.append(curr_node.word)
                curr_node.is_end_of_word = False  # To avoid duplicate entries
            
            # Mark the current cell as visited
            board[r][c] = '#'
            
            # Explore the neighbors in 4 directions
            for dr, dc in directions:
                nr, nc = r + dr, c + dc
                if 0 <= nr < rows and 0 <= nc < cols and board[nr][nc] in curr_node.children:
                    backtrack(nr, nc, curr_node)
            
            # Unmark the current cell (backtrack)
            board[r][c] = letter
            
            # Optimization: prune the node if it has no children
            if not curr_node.children:
                parent.children.pop(letter)
        
        # Start backtracking from each cell
        for r in range(rows):
            for c in range(cols):
                if board[r][c] in trie.root.children:
                    backtrack(r, c, trie.root)
        
        return result

Explanation:

  1. Trie Construction:
    • We use a Trie (prefix tree) to store the list of words. This allows us to quickly check prefixes while traversing the board.
  2. Backtracking:
    • The backtracking function explores all possible directions (up, down, left, right) from the current cell.
    • If a valid word is found, it is added to the result list, and the Trie node is marked to avoid duplicate entries.
  3. Pruning:
    • After backtracking, we prune the Trie by removing nodes that no longer lead to any words. This helps optimize the search.
  4. Handling Edge Cases:
    • The algorithm handles the case where the word list is empty, the board is small, or no valid words can be found.

Solution in Javascript

JavaScript
class TrieNode {
    constructor() {
        this.children = {};
        this.isEndOfWord = false;
        this.word = null;  // Store the complete word at the end node for easy retrieval
    }
}

class Trie {
    constructor() {
        this.root = new TrieNode();
    }
    
    // Insert a word into the Trie
    insert(word) {
        let node = this.root;
        for (let char of word) {
            if (!node.children[char]) {
                node.children[char] = new TrieNode();
            }
            node = node.children[char];
        }
        node.isEndOfWord = true;
        node.word = word;
    }
}

/**
 * @param {character[][]} board
 * @param {string[]} words
 * @return {string[]}
 */
var findWords = function(board, words) {
    // Initialize the Trie and insert all words
    const trie = new Trie();
    for (let word of words) {
        trie.insert(word);
    }
    
    // Result array to store found words
    const result = [];
    
    // Get the dimensions of the board
    const rows = board.length;
    const cols = board[0].length;
    
    // Directions for moving in the grid (up, down, left, right)
    const directions = [[-1, 0], [1, 0], [0, -1], [0, 1]];
    
    // Define the backtracking function
    function backtrack(r, c, parent) {
        let letter = board[r][c];
        let currNode = parent.children[letter];
        
        // Check if we found a word
        if (currNode.isEndOfWord) {
            result.push(currNode.word);
            currNode.isEndOfWord = false;  // Avoid duplicate entries
        }
        
        // Mark the current cell as visited
        board[r][c] = '#';
        
        // Explore the neighbors in 4 directions
        for (let [dr, dc] of directions) {
            let nr = r + dr;
            let nc = c + dc;
            if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && board[nr][nc] in currNode.children) {
                backtrack(nr, nc, currNode);
            }
        }
        
        // Unmark the current cell (backtrack)
        board[r][c] = letter;
        
        // Optimization: prune the node if it has no children
        if (Object.keys(currNode.children).length === 0) {
            delete parent.children[letter];
        }
    }
    
    // Start backtracking from each cell on the board
    for (let r = 0; r < rows; r++) {
        for (let c = 0; c < cols; c++) {
            if (board[r][c] in trie.root.children) {
                backtrack(r, c, trie.root);
            }
        }
    }
    
    return result;
};

Solution in Java

Java
import java.util.ArrayList;
import java.util.List;

class TrieNode {
    TrieNode[] children = new TrieNode[26];
    boolean isEndOfWord = false;
    String word = null;
}

class Trie {
    TrieNode root = new TrieNode();
    
    public void insert(String word) {
        TrieNode node = root;
        for (char c : word.toCharArray()) {
            int index = c - 'a';
            if (node.children[index] == null) {
                node.children[index] = new TrieNode();
            }
            node = node.children[index];
        }
        node.isEndOfWord = true;
        node.word = word;
    }
}

class Solution {
    private List<String> result = new ArrayList<>();
    private int[][] directions = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
    private int rows;
    private int cols;
    
    public List<String> findWords(char[][] board, String[] words) {
        Trie trie = new Trie();
        for (String word : words) {
            trie.insert(word);
        }
        
        rows = board.length;
        cols = board[0].length;
        
        for (int r = 0; r < rows; r++) {
            for (int c = 0; c < cols; c++) {
                if (trie.root.children[board[r][c] - 'a'] != null) {
                    backtrack(r, c, trie.root, board);
                }
            }
        }
        
        return result;
    }
    
    private void backtrack(int r, int c, TrieNode parent, char[][] board) {
        char letter = board[r][c];
        TrieNode currNode = parent.children[letter - 'a'];
        
        if (currNode.isEndOfWord) {
            result.add(currNode.word);
            currNode.isEndOfWord = false;
        }
        
        board[r][c] = '#';
        
        for (int[] direction : directions) {
            int nr = r + direction[0];
            int nc = c + direction[1];
            if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && board[nr][nc] != '#' && currNode.children[board[nr][nc] - 'a'] != null) {
                backtrack(nr, nc, currNode, board);
            }
        }
        
        board[r][c] = letter;
        
        if (isLeafNode(currNode)) {
            parent.children[letter - 'a'] = null;
        }
    }
    
    private boolean isLeafNode(TrieNode node) {
        for (int i = 0; i < 26; i++) {
            if (node.children[i] != null) {
                return false;
            }
        }
        return true;
    }
}

Solution in C#

C#
using System;
using System.Collections.Generic;

public class TrieNode {
    // Map each character to its corresponding TrieNode
    public TrieNode[] children = new TrieNode[26];
    // Flag to indicate if this node represents the end of a word
    public bool isEndOfWord = false;
    // Store the complete word at the end node for easy retrieval
    public string word = null;
}

public class Trie {
    // The root node of the Trie
    public TrieNode root = new TrieNode();
    
    // Method to insert a word into the Trie
    public void Insert(string word) {
        TrieNode node = root;
        foreach (char c in word) {
            int index = c - 'a';
            if (node.children[index] == null) {
                node.children[index] = new TrieNode();
            }
            node = node.children[index];
        }
        node.isEndOfWord = true;
        node.word = word;
    }
}

public class Solution {
    private IList<string> result = new List<string>();
    private int[][] directions = new int[][] {
        new int[] {-1, 0},  // up
        new int[] {1, 0},   // down
        new int[] {0, -1},  // left
        new int[] {0, 1}    // right
    };
    private int rows;
    private int cols;
    
    public IList<string> FindWords(char[][] board, string[] words) {
        // Initialize the Trie and insert all words
        Trie trie = new Trie();
        foreach (string word in words) {
            trie.Insert(word);
        }
        
        // Get the dimensions of the board
        rows = board.Length;
        cols = board[0].Length;
        
        // Start backtracking from each cell on the board
        for (int r = 0; r < rows; r++) {
            for (int c = 0; c < cols; c++) {
                if (trie.root.children[board[r][c] - 'a'] != null) {
                    Backtrack(r, c, trie.root, board);
                }
            }
        }
        
        return result;
    }
    
    private void Backtrack(int r, int c, TrieNode parent, char[][] board) {
        char letter = board[r][c];
        TrieNode currNode = parent.children[letter - 'a'];
        
        // Check if we have found a word
        if (currNode.isEndOfWord) {
            result.Add(currNode.word);
            currNode.isEndOfWord = false;  // Avoid duplicate entries
        }
        
        // Mark the current cell as visited by replacing the letter with a special character
        board[r][c] = '#';
        
        // Explore the neighbors in 4 directions
        foreach (int[] direction in directions) {
            int nr = r + direction[0];
            int nc = c + direction[1];
            if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && board[nr][nc] != '#' && currNode.children[board[nr][nc] - 'a'] != null) {
                Backtrack(nr, nc, currNode, board);
            }
        }
        
        // Unmark the current cell (backtrack)
        board[r][c] = letter;
        
        // Optimization: prune the node if it has no children
        if (IsLeafNode(currNode)) {
            parent.children[letter - 'a'] = null;
        }
    }
    
    // Helper function to check if a TrieNode is a leaf node (has no children)
    private bool IsLeafNode(TrieNode node) {
        for (int i = 0; i < 26; i++) {
            if (node.children[i] != null) {
                return false;
            }
        }
        return true;
    }
}

Subscribe
Notify of

0 Comments
Inline Feedbacks
View all comments

Popular