150NeetCode 150

61.Implement Trie (Prefix Tree)

MediumBlind 75Tries Ā· 1/3

Problem

A trie (prefix tree) stores a set of strings so that prefix questions can be answered quickly. Implement the Trie class:

  • Trie() initializes an empty trie.
  • void insert(String word) inserts word.
  • boolean search(String word) returns true if word was inserted before (as a whole word).
  • boolean startsWith(String prefix) returns true if any inserted word starts with prefix.

Examples

Input:  ["Trie","insert","search","search","startsWith","insert","search"]
        [[],["apple"],["apple"],["app"],["app"],["app"],["app"]]
Output: [null,null,true,false,true,null,true]
Explanation: "app" is only a prefix of "apple" until "app" itself is inserted.
Input:  ["Trie","insert","insert","startsWith","search","startsWith"]
        [[],["car"],["cat"],["ca"],["ca"],["cb"]]
Output: [null,null,null,true,false,false]
Explanation: "ca" prefixes both words but was never inserted; nothing starts with "cb".

Constraints

  • 1 <= word.length, prefix.length <= 2000
  • word and prefix consist only of lowercase English letters.
  • At most 3 * 10^4 calls in total to insert, search, and startsWith.

šŸ’” Key Insight

Words that share a prefix should share storage. A trie stores one node per distinct prefix: walking down L edges answers "is this a prefix?" in O(L), independent of how many words are stored. An isEnd flag on a node distinguishes "a word ends here" from "this is merely a prefix of a longer word".

Brute Force (List of Words)

TimeO(1) amortized for insert; O(n Ā· L) for search and startsWithSpaceO(total characters inserted)

Intuition

Keep every inserted word in a list. search checks for an equal string, startsWith checks every word with String.startsWith. It is correct but every query scans all n stored words.

Algorithm

  1. insert: append word to words.
  2. search: return true if any stored word equals(word).
  3. startsWith: return true if any stored word startsWith(prefix).

Visualization

words = ["apple", "app"]

startsWith("ap"):
  "apple".startsWith("ap") -> true  -> return true
search("appl"):
  "apple".equals("appl") -> false
  "app".equals("appl")   -> false   -> return false

Code

java
import java.util.ArrayList;
import java.util.List;

class Trie {
    private final List<String> words = new ArrayList<>();

    public Trie() {
    }

    public void insert(String word) {
        words.add(word);
    }

    public boolean search(String word) {
        for (String w : words) {
            if (w.equals(word)) {
                return true;
            }
        }
        return false;
    }

    public boolean startsWith(String prefix) {
        for (String w : words) {
            if (w.startsWith(prefix)) {
                return true;
            }
        }
        return false;
    }
}

Complexity

  • Time: O(1) amortized for insert; O(n Ā· L) for search and startsWith — up to n stored words, each comparison costs up to L characters.
  • Space: O(total characters inserted) — every word stored separately, with no prefix sharing.

Complexity Comparison

Approach Time (insert / search / startsWith) Space
List of Words O(1) / O(nĀ·L) / O(nĀ·L) O(total chars)
Trie (HashMap) O(L) / O(L) / O(L) O(total chars)
Trie (Array) O(L) / O(L) / O(L) O(nodes Ā· 26)

Common Pitfalls

  • Returning true from search just because the path exists — search("app") after inserting only "apple" must be false; check isEnd.
  • Forgetting to set isEnd when inserting a word that is a prefix of an existing one (no new nodes are created, but the flag must still be set).
  • Using c directly as an index instead of c - 'a' (index out of bounds).
  • Sharing one static root across instances — LeetCode creates a fresh Trie per test, so keep root an instance field.