2Brute2Best
🔥0

Implement Trie (Prefix Tree)

Medium·LeetCode #208Basic Anchor
LeetCode
30-Second Mental ModelTrie (prefix tree) with an end-of-word flag
Complexity:O(N · L)→O(L)
Key Insight

Each node has 26 child slots, one per letter, and an isEnd flag. Insert walks down from the root, creating missing nodes, and sets isEnd on the last node. Search and startsWith walk the same path but never create anything. The path answers the prefix question, and only the isEnd flag answers the exact-word question.

Core Validity Invariant

A node exists for a prefix exactly when some stored word starts with that prefix, and isEnd is true exactly when that prefix is itself a stored word.

01

Problem

Build a Trie class. It stores words and answers two questions about them:

  • search(word) is true only if this exact word was stored.
  • startsWith(prefix) is true if at least one stored word begins with prefix.

A shorter piece of a stored word is a prefix, not a word. After apple is stored, search("app") is false, but startsWith("app") is true.

Trie() makes an empty trie, and insert(word) stores a word.

Input

The calls in order: Trie() first, then any mix of insert, search and startsWith. Every word and prefix uses only lowercase letters a to z.

Output

For each search and startsWith, true or false. Trie() and insert return nothing (shown as null below).

Example 1
Input: Trie(), insert("apple"), search("apple"), search("app"), startsWith("app"), insert("app"), search("app")
Output: null, null, true, false, true, null, true

After apple is stored, apple is found, but app is not: it is only a prefix. startsWith("app") is true because apple starts with app. Once app is inserted as a word, search("app") is true.

Example 2
Input: Trie(), insert("cat"), startsWith("ca"), search("ca"), search("dog")
Output: null, null, true, false, false

cat starts with ca, so startsWith("ca") is true. But ca was never stored as a word, and dog was never stored at all.

Constraints
  • • 1 ≤ word.length, prefix.length ≤ 2000
  • • word and prefix use only lowercase letters a to z
  • • At most 3 · 10^4 calls in total to insert, search and startsWith
02

First, think yourself

Before opening any solution, try to answer these on paper. Use hints only when stuck — each hint is counted.

  1. 1.

    If I store apple as one whole string, how would I check app? Could I reuse anything I did for apple?

  2. 2.

    Words that start with the same letters, like app and apple, could share their first letters. What would that look like as a tree?

  3. 3.

    What does one node need to hold? Think about the letters that can come next, and whether a word stops right there.

  4. 4.

    search("app") and startsWith("app") walk the same letters. What is the one difference in their answers?

  5. 5.

    When a letter has no node during a search, what can I say about the answer? What changes during an insert?

03

Brute force approach

05

Optimized approach

06

Why does this algorithm work?

07

Dry run

08

All approaches compared

ApproachIdeaTimeSpace
Brute ForceStore words in a vector; search and startsWith check every stored wordO(N · L)O(T)
OptimalOne node per letter, children by letter, and a flag where a word endsO(L)O(26 · T)
09

Why does this belong to this pattern?

Clues in the problem statement
  • “many words”Words share their first letters, so store each letter once as a node and let words share the top of the tree.
  • “prefix”A prefix is just a path from the root, so checking it is a walk down the tree with no scanning.
  • “exact word vs prefix”Two questions on the same path. The path answers the prefix question, and one flag per node answers the word question.
one node per letter+a path from the root = a prefix+isEnd flag = a whole stored word→Trie (prefix tree) with an end-of-word flag

This is the anchor of the Trie pattern. A prefix question is a walk, and the walk either finishes or falls off. The path tells you about prefixes, and the isEnd flag tells you about whole words. Because apple and app share their first three nodes, each shared letter is stored and checked once. Later trie questions build on this: Replace Words stops at the first end flag it meets, and Search Suggestions keeps a short list at each node.

10

Don't make this mistake

Pattern confusion: search returns true for a prefix
✗ bool search(string word) { return walk(word) != nullptr; // forgot isEnd }
✓ bool search(string word) { Node* node = walk(word); return node != nullptr && node->isEnd; }

Insert apple, then call search with app. The walk reaches the second p, so the wrong version returns true. The right answer is false, because app was never inserted as a word. Only the isEnd flag says a word stops there.

startsWith checks isEnd instead of the walk
✗ bool startsWith(string prefix) { Node* node = walk(prefix); return node != nullptr && node->isEnd; }
✓ bool startsWith(string prefix) { return walk(prefix) != nullptr; }

Insert apple, then call startsWith with app. The path exists, so the answer is true. The wrong version checks isEnd, gets false, and says no stored word starts with app, which is wrong. A prefix does not need to be a word.

Following a missing child (crash)
✗ Node* cur = root; for (char ch : word) cur = cur->children[ch - 'a']; // cur may become nullptr return cur->isEnd;
✓ Node* cur = root; for (char ch : word) { cur = cur->children[ch - 'a']; if (!cur) return false; // no node for this letter: stop } return cur->isEnd;

Insert apple, then search apply. The walk goes a, p, p, l, then y has no node, so cur becomes nullptr. The last line reads cur->isEnd on nothing, and the program crashes. Stop as soon as a letter is missing.

Replacing a node that already exists
✗ for (char ch : word) { cur->children[ch - 'a'] = new Node(); // throws away the old node and its subtree cur = cur->children[ch - 'a']; }
✓ Node*& child = cur->children[ch - 'a']; if (!child) child = new Node(); // reuse the node if it is already there cur = child;

Insert apple, then insert app. The wrong version replaces the first a node, and with it the whole p, p, l, e branch. Then search apple returns false, even though it was inserted. Only create a node when its slot is empty.

11

Want to implement this?

Write it yourself on LeetCode, then come back and compare your code with the solutions above.

Open coding workspace
How did it go?
Beyond The LeetCode Problem

How this concept connects to System Design & C++ Systems

Bridge DSA with production engineering

Top engineering interviews test whether you understand how algorithms like Trie translate into production architectures and low-level memory mechanics.