Trie (Prefix Tree)
A specialized tree-based data structure used for efficient retrieval of keys in a dataset of strings
Trie (Prefix Tree)
Description
A Trie, also called a prefix tree, is a specialized tree-based data structure used for efficient retrieval of keys in a dataset of strings. Each node in the trie represents a single character of a string. The path from the root to a particular node forms a prefix of one or more strings in the dataset. Tries are particularly useful for implementing autocomplete features, spell checkers, and IP routing.
Use Cases
- Autocomplete suggestions
- Spell checking
- Prefix matching
- Word games (like finding all words in a boggle board)
- IP routing (longest prefix matching)
- T9 predictive text
- Implementing dictionaries with prefix operations
Code Example
/**
* Trie node structure
*/
class TrieNode {
children: Map<string, TrieNode>;
isEndOfWord: boolean;
constructor() {
this.children = new Map();
this.isEndOfWord = false;
}
}
/**
* Trie (Prefix Tree) implementation
*/
class Trie {
private root: TrieNode;
constructor() {
this.root = new TrieNode();
}
/**
* Insert a word into the trie
* @param word Word to insert
*/
insert(word: string): void {
let current = this.root;
for (const char of word) {
if (!current.children.has(char)) {
current.children.set(char, new TrieNode());
}
current = current.children.get(char)!;
}
// Mark the end of the word
current.isEndOfWord = true;
}
/**
* Search for a word in the trie
* @param word Word to search for
* @returns True if the word exists in the trie, false otherwise
*/
search(word: string): boolean {
let current = this.root;
for (const char of word) {
if (!current.children.has(char)) {
return false;
}
current = current.children.get(char)!;
}
// Check if this is a complete word
return current.isEndOfWord;
}
/**
* Check if there is any word in the trie that starts with the given prefix
* @param prefix Prefix to search for
* @returns True if there is any word with the given prefix, false otherwise
*/
startsWith(prefix: string): boolean {
let current = this.root;
for (const char of prefix) {
if (!current.children.has(char)) {
return false;
}
current = current.children.get(char)!;
}
return true;
}
/**
* Get all words in the trie with the given prefix
* @param prefix Prefix to search for
* @returns Array of words with the given prefix
*/
getWordsWithPrefix(prefix: string): string[] {
const result: string[] = [];
let current = this.root;
// Navigate to the node corresponding to the prefix
for (const char of prefix) {
if (!current.children.has(char)) {
return result;
}
current = current.children.get(char)!;
}
// Perform DFS from the prefix node to find all words
this.dfs(current, prefix, result);
return result;
}
/**
* Helper function to perform DFS and collect words
* @param node Current node
* @param prefix Current prefix
* @param result Array to store the words
*/
private dfs(node: TrieNode, prefix: string, result: string[]): void {
// If this is the end of a word, add it to the result
if (node.isEndOfWord) {
result.push(prefix);
}
// Explore all children
for (const [char, childNode] of node.children.entries()) {
this.dfs(childNode, prefix + char, result);
}
}
/**
* Delete a word from the trie
* @param word Word to delete
* @returns True if the word was deleted, false if it doesn't exist
*/
delete(word: string): boolean {
return this.deleteHelper(this.root, word, 0);
}
/**
* Helper function to recursively delete a word
* @param node Current node
* @param word Word to delete
* @param depth Current depth in the trie
* @returns True if the node should be deleted, false otherwise
*/
private deleteHelper(node: TrieNode, word: string, depth: number): boolean {
// If we've reached the end of the word
if (depth === word.length) {
// If the word doesn't exist in the trie
if (!node.isEndOfWord) {
return false;
}
// Mark the end of the word as false
node.isEndOfWord = false;
// Return true if this node has no children (can be deleted)
return node.children.size === 0;
}
const char = word[depth];
// If the character doesn't exist in the trie
if (!node.children.has(char)) {
return false;
}
const childNode = node.children.get(char)!;
const shouldDeleteChild = this.deleteHelper(childNode, word, depth + 1);
// If the child should be deleted
if (shouldDeleteChild) {
node.children.delete(char);
// Return true if this node has no children and is not the end of a word
return node.children.size === 0 && !node.isEndOfWord;
}
return false;
}
}
// Example usage
const trie = new Trie();
// Insert words
trie.insert("apple");
trie.insert("app");
trie.insert("application");
trie.insert("banana");
trie.insert("ball");
// Search for words
console.log("Search 'apple':", trie.search("apple")); // true
console.log("Search 'app':", trie.search("app")); // true
console.log("Search 'appl':", trie.search("appl")); // false
console.log("Search 'orange':", trie.search("orange")); // false
// Check prefixes
console.log("Starts with 'app':", trie.startsWith("app")); // true
console.log("Starts with 'ban':", trie.startsWith("ban")); // true
console.log("Starts with 'ora':", trie.startsWith("ora")); // false
// Get words with prefix
console.log("Words with prefix 'app':", trie.getWordsWithPrefix("app"));
// Output: Words with prefix 'app': ["app", "apple", "application"]
// Delete a word
console.log("Delete 'apple':", trie.delete("apple")); // true
console.log("Search 'apple' after deletion:", trie.search("apple")); // false
console.log("Search 'app' after deletion:", trie.search("app")); // true
LeetCode Example Problems
- LeetCode 208. Implement Trie (Prefix Tree)
- LeetCode 211. Design Add and Search Words Data Structure
- LeetCode 212. Word Search II
- LeetCode 648. Replace Words
- LeetCode 677. Map Sum Pairs
Complexity Analysis
-
Time Complexity:
- Insertion: O(m) where m is the length of the word being inserted.
- Search: O(m) where m is the length of the word being searched.
- Prefix Search: O(m) where m is the length of the prefix.
- Get Words with Prefix: O(n) where n is the total number of characters in all words with the given prefix.
- Deletion: O(m) where m is the length of the word being deleted.
-
Space Complexity: O(n * m) where n is the number of words and m is the average length of the words. In the worst case, if there are no common prefixes, the space complexity would be the sum of the lengths of all words.
When to Use
Use a Trie when:
- You need to efficiently store and retrieve strings
- You need to perform prefix-based operations (like autocomplete)
- You're working with a large dictionary of words
- You need to check if a string is a prefix of any string in a dataset
- You're implementing a spell checker or a word game
- You need to find all words with a common prefix