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:
Where is the number of rows in theboard, is the number of columns in theboardand 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 where is the number of characters in the
wordslist.Space:
Where 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.