Skip to main content

Trie (Prefix Tree)

Learn about the Trie data structure and its applications in string processing and dictionary problems.

Trie Fundamentals

What is a Trie?

A Trie (pronounced "try") is a tree-like data structure that stores strings in a way that makes prefix-based operations efficient. Each node represents a character, and paths from root to leaf represent complete strings.

Basic Structure

class TrieNode {
public:
TrieNode* children[26]; // For lowercase English letters
bool isEndOfWord;

TrieNode() {
for (int i = 0; i < 26; i++) {
children[i] = nullptr;
}
isEndOfWord = false;
}
};

Trie Class Implementation

class Trie {
private:
TrieNode* root;

public:
Trie() {
root = new TrieNode();
}

// Insert a word into the trie
void insert(string word) {
TrieNode* current = root;

for (char c : word) {
int index = c - 'a';
if (current->children[index] == nullptr) {
current->children[index] = new TrieNode();
}
current = current->children[index];
}

current->isEndOfWord = true;
}

// Search for a word in the trie
bool search(string word) {
TrieNode* current = root;

for (char c : word) {
int index = c - 'a';
if (current->children[index] == nullptr) {
return false;
}
current = current->children[index];
}

return current->isEndOfWord;
}

// Check if any word starts with the given prefix
bool startsWith(string prefix) {
TrieNode* current = root;

for (char c : prefix) {
int index = c - 'a';
if (current->children[index] == nullptr) {
return false;
}
current = current->children[index];
}

return true;
}
};

String Prefix Problems

Longest Common Prefix

// Find the longest common prefix of all strings
string longestCommonPrefix(vector<string>& strs) {
if (strs.empty()) return "";

string prefix = strs[0];

for (int i = 1; i < strs.size(); i++) {
while (strs[i].find(prefix) != 0) {
prefix = prefix.substr(0, prefix.length() - 1);
if (prefix.empty()) return "";
}
}

return prefix;
}

Prefix Matching

// Find all words that start with a given prefix
vector<string> findWordsWithPrefix(TrieNode* root, string prefix) {
vector<string> result;
TrieNode* current = root;

// Navigate to the prefix node
for (char c : prefix) {
int index = c - 'a';
if (current->children[index] == nullptr) {
return result; // No words with this prefix
}
current = current->children[index];
}

// DFS to find all words starting from this node
dfs(current, prefix, result);
return result;
}

void dfs(TrieNode* node, string currentWord, vector<string>& result) {
if (node->isEndOfWord) {
result.push_back(currentWord);
}

for (int i = 0; i < 26; i++) {
if (node->children[i] != nullptr) {
char c = 'a' + i;
dfs(node->children[i], currentWord + c, result);
}
}
}

Word Search in 2D Grid

// Check if a word exists in a 2D grid using Trie
bool wordSearch(vector<vector<char>>& board, vector<string>& words) {
Trie trie;
for (string word : words) {
trie.insert(word);
}

int m = board.size();
int n = board[0].size();
vector<string> result;

for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
dfs(board, i, j, trie.root, "", result);
}
}

return !result.empty();
}

void dfs(vector<vector<char>>& board, int i, int j, TrieNode* node,
string word, vector<string>& result) {
if (i < 0 || i >= board.size() || j < 0 || j >= board[0].size()) {
return;
}

char c = board[i][j];
if (c == '#' || node->children[c - 'a'] == nullptr) {
return;
}

node = node->children[c - 'a'];
word += c;

if (node->isEndOfWord) {
result.push_back(word);
node->isEndOfWord = false; // Avoid duplicates
}

board[i][j] = '#'; // Mark as visited

// Explore all four directions
dfs(board, i + 1, j, node, word, result);
dfs(board, i - 1, j, node, word, result);
dfs(board, i, j + 1, node, word, result);
dfs(board, i, j - 1, node, word, result);

board[i][j] = c; // Restore
}

Autocomplete

Autocomplete System

class AutocompleteSystem {
private:
TrieNode* root;
string currentQuery;

public:
AutocompleteSystem(vector<string> sentences, vector<int> times) {
root = new TrieNode();
for (int i = 0; i < sentences.size(); i++) {
insertWithFrequency(sentences[i], times[i]);
}
}

void insertWithFrequency(string sentence, int frequency) {
TrieNode* current = root;

for (char c : sentence) {
int index = c - 'a';
if (current->children[index] == nullptr) {
current->children[index] = new TrieNode();
}
current = current->children[index];
}

current->isEndOfWord = true;
current->frequency = frequency;
}

vector<string> input(char c) {
if (c == '#') {
insertWithFrequency(currentQuery, 1);
currentQuery = "";
return {};
}

currentQuery += c;
return getTopSuggestions(currentQuery, 3);
}

vector<string> getTopSuggestions(string prefix, int k) {
vector<pair<string, int>> suggestions;
TrieNode* current = root;

// Navigate to prefix node
for (char c : prefix) {
int index = c - 'a';
if (current->children[index] == nullptr) {
return {};
}
current = current->children[index];
}

// Collect all suggestions
collectSuggestions(current, prefix, suggestions);

// Sort by frequency and return top k
sort(suggestions.begin(), suggestions.end(),
[](const pair<string, int>& a, const pair<string, int>& b) {
return a.second > b.second;
});

vector<string> result;
for (int i = 0; i < min(k, (int)suggestions.size()); i++) {
result.push_back(suggestions[i].first);
}

return result;
}
};

Dictionary Problems

Word Break

// Check if a string can be segmented into dictionary words
bool wordBreak(string s, vector<string>& wordDict) {
Trie trie;
for (string word : wordDict) {
trie.insert(word);
}

int n = s.length();
vector<bool> dp(n + 1, false);
dp[0] = true;

for (int i = 1; i <= n; i++) {
for (int j = 0; j < i; j++) {
if (dp[j] && trie.search(s.substr(j, i - j))) {
dp[i] = true;
break;
}
}
}

return dp[n];
}

Word Break II

// Find all possible sentences from word break
vector<string> wordBreakII(string s, vector<string>& wordDict) {
Trie trie;
for (string word : wordDict) {
trie.insert(word);
}

vector<string> result;
vector<string> current;

dfs(s, 0, trie.root, current, result);
return result;
}

void dfs(string s, int start, TrieNode* root, vector<string>& current,
vector<string>& result) {
if (start == s.length()) {
string sentence = "";
for (int i = 0; i < current.size(); i++) {
if (i > 0) sentence += " ";
sentence += current[i];
}
result.push_back(sentence);
return;
}

TrieNode* current = root;
for (int i = start; i < s.length(); i++) {
int index = s[i] - 'a';
if (current->children[index] == nullptr) {
break;
}

current = current->children[index];
if (current->isEndOfWord) {
current.push_back(s.substr(start, i - start + 1));
dfs(s, i + 1, root, current, result);
current.pop_back();
}
}
}

Advanced Trie Operations

Trie with Frequency

class TrieNodeWithFreq {
public:
TrieNodeWithFreq* children[26];
bool isEndOfWord;
int frequency;

TrieNodeWithFreq() {
for (int i = 0; i < 26; i++) {
children[i] = nullptr;
}
isEndOfWord = false;
frequency = 0;
}
};

Trie with Deletion

// Delete a word from the trie
bool deleteWord(TrieNode* root, string word, int index = 0) {
if (index == word.length()) {
if (!root->isEndOfWord) return false;
root->isEndOfWord = false;
return isEmpty(root);
}

int charIndex = word[index] - 'a';
if (root->children[charIndex] == nullptr) return false;

bool shouldDelete = deleteWord(root->children[charIndex], word, index + 1);

if (shouldDelete) {
delete root->children[charIndex];
root->children[charIndex] = nullptr;
return isEmpty(root) && !root->isEndOfWord;
}

return false;
}

bool isEmpty(TrieNode* node) {
for (int i = 0; i < 26; i++) {
if (node->children[i] != nullptr) {
return false;
}
}
return true;
}

Performance Analysis

Time Complexity

  • Insert: O(m) where m is the length of the word
  • Search: O(m) where m is the length of the word
  • Prefix Search: O(m) where m is the length of the prefix
  • Space: O(ALPHABET_SIZE * N * M) where N is the number of words and M is the average length

Space Optimization

  • Compressed Trie: Merge nodes with single children
  • Ternary Search Tree: Alternative to trie with less space
  • Suffix Tree: For suffix-based operations

Common Patterns

  1. String matching and prefix operations
  2. Autocomplete and suggestion systems
  3. Word games and puzzles
  4. Dictionary operations and text processing
  5. Pattern matching in strings

Applications

  • Search engines: Autocomplete and suggestion
  • Spell checkers: Dictionary lookup
  • IP routing: Longest prefix matching
  • Bioinformatics: DNA sequence analysis
  • Text editors: Code completion