In the realm of data structures and algorithms, the Trie, also known as a Prefix Tree, stands out as a powerful and efficient tool for various applications. In this article, we’ll delve deep into the implementation of Trie, exploring its structure, functionalities, and practical use cases. Moreover, we’ll tackle 75 LeetCode questions related to Trie, ensuring you’re well-equipped to handle blind challenges.
Understanding Trie
An Overview
Before we dive into the intricacies of Trie implementation, let’s establish a solid understanding of what a Trie is and why it’s a valuable data structure.
What is a Trie?
A Trie, short for retrieval tree or prefix tree, is a tree-like data structure that is used to store an associative array where keys are usually strings. Unlike other data structures like arrays, linked lists, or binary search trees, Tries are specifically designed for handling strings efficiently.
Structure of a Trie
A Trie comprises nodes, each representing a single character of a string. The nodes are connected in a hierarchical fashion, forming a tree structure. The root node represents an empty string, and each subsequent node corresponds to a character in the string.
Advantages of Using a Trie
Tries offer several advantages, making them suitable for various applications
Efficient Search Operations
Tries provide quick and efficient search operations, especially when dealing with large datasets.
Prefix Matching
Tries excel in prefix matching, making them ideal for autocomplete and dictionary applications.
Space Efficiency
Despite their apparent overhead, Tries can be space-efficient, especially when storing a large number of strings with common prefixes.
Dynamic Operations
Tries support dynamic operations like insertion and deletion efficiently.
Implementing a Trie
Step by Step
Now that we have a foundational understanding of Trie, let’s move on to the practical aspects of implementing it. We’ll break down the process into manageable steps to ensure clarity.
Define the TrieNode Class
class TrieNode:
def __init__(self):
self.children = {}
self.is_end_of_word = False
The TrieNode
class represents a single node in the Trie. It contains a dictionary (children
) to store links to the next level of nodes and a boolean flag (is_end_of_word
) to indicate whether the current node represents the end of a word.
Initialize the Trie
class Trie:
def __init__(self):
self.root = TrieNode()
def insert(self, word):# Implementation of insertion will be covered in the next step
pass
The Trie
class initializes with a root node. We will now focus on the insert
method, responsible for adding words to the Trie.
Insertion Operation
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
The insert
method iterates through each character of the word and creates nodes as needed. The last node is marked as the end of the word.
Search Operation
def search(self, word):
node = self.root
for char in word:
if char not in node.children:
return False
node = node.children[char]
return node.is_end_of_word
The search
method checks if a given word exists in the Trie. It returns True
if the word is present and False
otherwise.
Prefix Matching
def starts_with_prefix(self, prefix):
node = self.root
for char in prefix:
if char not in node.children:
return False
node = node.children[char]
return True
The starts_with_prefix
method checks if there are any words in the Trie that start with a given prefix.
Practical Application
Blind 75 LeetCode Questions
With the basics of Trie implementation covered, let’s take our understanding to the next level by solving 75 LeetCode questions related to Trie. These questions will not only test your knowledge but also enhance your problem-solving skills.
LeetCode Question 1
Implement Trie (Prefix Tree)
Problem Statement: Implement a Trie with insert, search, and startsWith methods.
Solution
# Implementation of Trie with insert, search, and starts_with_prefix methods
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
def search(self, word):
node = self.root
for char in word:
if char not in node.children:
return False
node = node.children[char]
return node.is_end_of_word
def starts_with_prefix(self, prefix):
node = self.root
for char in prefix:
if char not in node.children:
return False
node = node.children[char]
return True
This solution demonstrates the implementation of a Trie class with the required methods.
Word Search II
Problem Statement: Given a 2D board and a list of words from the dictionary, find all words in the board.
Solution
def find_words(board, words):
trie = Trie()
for word in words:
trie.insert(word)
result = set()
def dfs(node, i, j, path):
char = board[i][j]
curr_node = node.children.get(char, None)
if not curr_node:
return
path += char
if curr_node.is_end_of_word:
result.add(path)
board[i][j] = “#” # Mark the cell as visited
# Explore neighbors
directions = [(-1, 0), (1, 0), (0, –1), (0, 1)]
for di, dj in directions:
ni, nj = i + di, j + dj
if 0 <= ni < len(board) and 0 <= nj < len(board[0]) and board[ni][nj] != “#”:
dfs(curr_node, ni, nj, path)
board[i][j] = char # Revert the cell to its original state
for i in range(len(board)):
for j in range(len(board[0])):
dfs(trie.root, i, j, “”)
return list(result)
This solution uses Trie to efficiently search for words in a 2D board.
Longest Word in Dictionary
Problem Statement
Given a list of words, find the longest word made of other words in the list.
Solution
def longest_word(words):
trie = Trie()
for word in sorted(words):
if trie.starts_with_prefix(word[:-1]):
trie.insert(word)
result = “”