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.
For example, consider the following board and list of words:
board = [
['o','a','a','n'],
['e','t','a','e'],
['i','h','k','r'],
['i','f','l','v']
]
words = ["oath","pea","eat","rain"]
The expected output is ["eat","oath"]
because those are the only two words that appear on the board following the adjacency rules.
As another example, consider the following board and word list:
board = [
['a','b'],
['c','d']
]
words = ["abcb"]
In this case, the expected output is []
because the word "abcb" cannot be constructed on the board following the adjacency rules (the 'b' would have to be visited twice).
Write a function that takes a 2D character array (the board) and a list of strings (the words) as input and returns a list of strings representing the words that can be found on the board.
When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:
Imagine you are searching for words in a grid of letters. The brute force approach is like checking every single possible path in the grid to see if it spells out any of your target words. It's thorough but not very smart.
Here's how the algorithm would work step-by-step:
def word_search_brute_force(board, words):
rows = len(board)
cols = len(board[0])
found_words = set()
def explore_path(row, col, current_word, visited):
# Base case: word found
if current_word in words:
found_words.add(current_word)
# Check boundaries and visited cells
if (row < 0 or row >= rows or col < 0 or col >= cols or
(row, col) in visited):
return
new_word = current_word + board[row][col]
# If no word starts with the current path, stop exploring
if not any(word.startswith(new_word) for word in words):
return
new_visited = set(visited)
new_visited.add((row, col))
# Explore adjacent cells
explore_path(row + 1, col, new_word, new_visited)
explore_path(row - 1, col, new_word, new_visited)
explore_path(row, col + 1, new_word, new_visited)
explore_path(row, col - 1, new_word, new_visited)
# Iterate through each cell as a starting point
for row_index in range(rows):
for col_index in range(cols):
# Explore all possible paths starting from this cell
explore_path(row_index, col_index, "", set())
# Convert set to list before returning
return list(found_words)
We're given a grid of letters and a list of words, and need to find all the words in the list that can be found in the grid. The efficient approach involves building a special data structure to store all the words, then searching the grid in a way that lets us quickly discard paths that can't possibly lead to a valid word.
Here's how the algorithm would work step-by-step:
class TrieNode:
def __init__(self):
self.children = {}
self.word = None
class Solution:
def findWords(self, board, words):
root_node = self.buildTrie(words)
found_words = set()
rows, cols = len(board), len(board[0])
def depthFirstSearch(row, col, node):
if row < 0 or row >= rows or col < 0 or col >= cols or board[row][col] == '#' or board[row][col] not in node.children:
return
char = board[row][col]
node = node.children[char]
# If we find a word, add it to results
if node.word:
found_words.add(node.word)
original_char = board[row][col]
board[row][col] = '#'
depthFirstSearch(row + 1, col, node)
depthFirstSearch(row - 1, col, node)
depthFirstSearch(row, col + 1, node)
depthFirstSearch(row, col - 1, node)
board[row][col] = original_char
for row in range(rows):
for col in range(cols):
depthFirstSearch(row, col, root_node)
return list(found_words)
def buildTrie(self, words):
root_node = TrieNode()
for word in words:
node = root_node
for char in word:
if char not in node.children:
node.children[char] = TrieNode()
node = node.children[char]
node.word = word
return root_node
Case | How to Handle |
---|---|
Empty board or empty words list | Return an empty list immediately as there are no words to search for or no board to search on. |
Board with one row and one column, and the words list contains a word of length 1 that matches the cell | The algorithm should correctly identify the one-letter word if it matches the single cell on the board. |
Words list contains duplicate words | The solution should avoid adding the same word multiple times to the result list by using a set or similar de-duplication mechanism. |
Words contain characters not present in the board | The algorithm should not find these words and should not throw any errors. |
Word is longer than the maximum possible path on the board (e.g., a word longer than board width * board height) | The depth-first search will terminate prematurely when it runs out of board space before finding the complete word. |
Words contain repeated characters leading to cycles if not carefully handled. | The backtracking algorithm must mark visited cells during a search to prevent infinite recursion due to cycles. |
Large board and many words, potentially leading to stack overflow with recursion. | The recursion depth should be considered and the implementation may need to be optimized, potentially using iterative approach for DFS. |
Board contains unicode characters. | Ensure that the character comparisons and lookups in the Trie and DFS work correctly with unicode characters. |