Trie · Medium
LeetCode 208: Implement Trie (Prefix Tree) — solution
A prefix tree where each node has 26 child slots and an end-of-word flag. Insert, search and startsWith all run in O(L) time for a string of length L.
Problem in plain words
Build a Trie class with three operations on lowercase words: insert(word) stores a word, search(word) reports whether that exact word was stored, and startsWith(prefix) reports whether any stored word begins with the given prefix.
For example, after inserting "tree", search("tre") is false (no such full word) but startsWith("tre") is true.
Intuition
Store words as paths in a tree where every edge is a letter. Words with the same beginning share the same path for that beginning, so a prefix lookup is just walking down the letters. One extra boolean per node marks where a complete word ends, which is what separates search from startsWith. The trie insert and search lesson animates exactly this walk.
Approach
- Each node holds
children, an array of 26 references (one per letter), andisEnd, a boolean. - insert: start at the root. For each letter, create the child if it is missing and step into it. At the end, set
isEnd = true. - Shared walk: write a helper that follows the letters of a string from the root and returns the last node reached, or null if some letter is missing.
- search: the helper must return a node and that node's
isEndmust be true. - startsWith: the helper must return a node; the flag does not matter.
Dry run
insert("car")creates root to c to a to r and marksras an end.insert("cat")reuses c and a, then creates a new childtunderaand marks it as an end.search("ca")reaches nodea, but itsisEndis false: returns false.startsWith("ca")reaches nodea: returns true.search("cat")reachestwithisEndtrue: returns true.search("cab")finds nobundera: returns false.
Java solution
class Trie {
private static class Node {
Node[] children = new Node[26];
boolean isEnd;
}
private final Node root;
public Trie() {
root = new Node();
}
public void insert(String word) {
Node node = root;
for (int i = 0; i < word.length(); i++) {
int idx = word.charAt(i) - 'a';
if (node.children[idx] == null) {
node.children[idx] = new Node();
}
node = node.children[idx];
}
node.isEnd = true;
}
public boolean search(String word) {
Node node = walk(word);
return node != null && node.isEnd;
}
public boolean startsWith(String prefix) {
return walk(prefix) != null;
}
private Node walk(String s) {
Node node = root;
for (int i = 0; i < s.length() && node != null; i++) {
node = node.children[s.charAt(i) - 'a'];
}
return node;
}
}Python solution
class TrieNode:
def __init__(self):
self.children = {}
self.is_end = False
class Trie:
def __init__(self):
self.root = TrieNode()
def insert(self, word: str) -> None:
node = self.root
for ch in word:
if ch not in node.children:
node.children[ch] = TrieNode()
node = node.children[ch]
node.is_end = True
def search(self, word: str) -> bool:
node = self._walk(word)
return node is not None and node.is_end
def startsWith(self, prefix: str) -> bool:
return self._walk(prefix) is not None
def _walk(self, s: str):
node = self.root
for ch in s:
node = node.children.get(ch)
if node is None:
return None
return nodeComplexity
| Measure | Cost | Why |
|---|---|---|
| Time | O(L) per operation | Each call visits one node per character of its argument. |
| Space | O(total characters * 26) worst case | Every inserted character may create a node with 26 child slots (the dictionary version stores only used children). |
Edge cases and common mistakes
- Prefix versus word: forgetting the
isEndcheck makessearch("ca")return true after inserting"cat". - Inserting the same word twice: the second insert just walks the existing path and sets the flag again, which is harmless.
- A word that is a prefix of another: inserting
"cat"after"cats"only sets the flag on the existingtnode; no new nodes are needed. - Null checks while walking: stop as soon as a child is missing, or you will dereference null.
›Array of 26 or a hash map for children?
An array gives O(1) indexing and is fastest for a small fixed alphabet. A map uses less memory when nodes are sparse or the alphabet is large, such as Unicode text.
