Problem Statement in English
You’re given a 2D board board, and a list of words, words. You need to find all the words in the list that can be formed by a sequence of adjacent characters in the board.
Approach
You need to know what a trie is. Check this problem out if not.
The mistake I made was adding the entire board to the trie. This is not needed. We only need to add words to the trie. In fact doing this will give you a TLE (Time Limit Exceeded).
From there the next thing you need to do is search through the trie with each cell in the board as a starting point. If you find a word, add it to the result set and remove it from the trie.
Removing the word from the trie can result in upto a 7x speedup (trust me, I figured this out the hard way). It’s quite dramatic really.
Solution in Python
class TrieNode:
def __init__(self) -> None:
self.children: dict[str, TrieNode] = {}
self.isWord = False
# while building the trie,
# everytime we add a character
# we increment its count by 1
#
# that way, later, we can keep track of
# how many times a character must be searched
#
# helps prevent duplicate searches
self.count = 0
class Solution:
def findWords(self, board: List[List[str]], words: List[str]) -> List[str]:
root = TrieNode()
# stores words that are in the grid and trie
res: set[str] = set()
# stores the indexes that have been seen
seen: set[tuple[int, int]] = set()
# remove `word` from the trie
def removeWord(word):
ptr = root
for c in word:
if c in ptr.children:
ptr = ptr.children[c]
ptr.count -= 1
# mark the node as not a word
# as we've already found it
ptr.isWord = False
# add `word` to the trie
def addWord(word):
node = root
for char in word:
if char not in node.children:
temp = TrieNode()
node.children[char] = temp
# move the pointer forward
node = node.children[char]
# increment the count of the node
node.count += 1
# mark the the node
# as the end of a word
node.isWord = True
# Build trie from words
for word in words:
addWord(word)
def recursiveCall(
i: int,
j: int,
trieNode: TrieNode,
word: str,
):
if (
i not in range(len(board))
or j not in range(len(board[i]))
or (i, j) in seen
or board[i][j] not in trieNode.children
or trieNode.children[board[i][j]].count < 1
):
return
else:
# the character we're currently looking at
# in the board
character = board[i][j]
# move the pointer forward
trieNode = trieNode.children[character]
# add the character to the word we're currently forming
word += character
# if we've reached a node
# that marks the end of a word
# add it to the result set
#
# and remove the word from the trie
# so that we don't check for it again
if trieNode.isWord:
res.add(word)
removeWord(word)
# mark this index as seen
seen.add((i, j))
# left cell
recursiveCall(i - 1, j, trieNode, word)
# right cell
recursiveCall(i + 1, j, trieNode, word)
# bottom cell
recursiveCall(i, j - 1, trieNode, word)
# top cell
recursiveCall(i, j + 1, trieNode, word)
# mark this index as unseen
# so that we can reuse the same set
# in the future
seen.remove((i, j))
for row in range(len(board)):
for col in range(len(board[row])):
# for each index in the grid search through the trie
recursiveCall(row, col, root, "")
# return the answer
return list(res)
Complexity
Time: $O(i \times j * n)$
Where $i$ is the number of rows in theboard, $j$ is the number of columns in theboardand $n$ is the number of characters in thewordslist.This is because for each cell in the board, we search through the trie. The trie search is $O(n)$ where $n$ is the number of characters in the
wordslist.Space: $O(n)$
Where $n$ number of characters in thewordslist.
Mistakes I Made
As I mentioned earlier, I added the entire board to the trie. I had to look at the solution to figure out what I was doing wrong.
Further, I didn’t think of removing the word from the trie after finding it. This is a very important step as it can result in a crucial speedup.
And we are done.