PracticeNeetCode ↗LeetCode ↗
Design a trie (prefix tree) that supports three operations: inserting a word, checking whether an exact word has been inserted, and checking whether any inserted word starts with a given prefix.
search and startsWith differ only in the final check — search requires the end-of-word flag, startsWith doesn't care.is_end.is_end = True at the last node.False. At the end, return the node's is_end flag.True as soon as the prefix is fully consumed, regardless of is_end.Time: O(L) per operation, where L is the word/prefix length — Space: O(N · L) across all inserted words in the worst case
class TrieNode:
def __init__(self):
self.children: dict[str, "TrieNode"] = {}
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.root
for ch in word:
if ch not in node.children:
return False
node = node.children[ch]
return node.is_end
def startsWith(self, prefix: str) -> bool:
node = self.root
for ch in prefix:
if ch not in node.children:
return False
node = node.children[ch]
return TrueHow the solution above works, piece by piece.
TrieNode has children (character → node) and is_end, which marks that a whole word stops here.insert: walk from the root, creating a child for each character that doesn't exist yet, then set is_end on the last node.search: follow the characters; a missing child means the word isn't there. At the end, return is_end, because a path alone might just be a prefix of a longer word ("app" in "apple").startsWith: same walk, but any complete path counts, so return True.Running the example calls:
search and startsWith is whether the last node must be marked as the end of a word.