Replace Words
Given a dictionary of root words and a sentence, replace every word in the sentence with its shortest root from the dictionary. If a word has no root, leave it unchanged.
Intuition
For each word in the sentence you need to find the shortest prefix that appears in the dictionary. A hash set works — check every prefix of increasing length — but a trie answers this in a single walk: feed the word character by character, and the first node you hit that is marked as a word-end is the shortest root. You stop immediately, without ever scanning past the shortest match. The trie also naturally handles the case where one root is a prefix of another, because you encounter the shorter root first.
When you need to find the shortest or longest prefix of a string that belongs to a known set, a trie gives you the answer in a single walk. The pattern shows up whenever the problem says 'dictionary of roots', 'prefix matching', or 'replace by shortest match'. Hash sets can simulate this with a loop over prefix lengths, but the trie is the natural fit.
Approach
Build a trie from the dictionary roots
Insert each root word character by character into a trie. Mark the final node of each root with a flag (or store the root itself). This costs O(total characters in dictionary). The trie compresses shared prefixes, so roots like cat and cattle share the c-a-t path.
Walk each sentence word through the trie, stopping at the first root hit
For each word, traverse the trie one character at a time. If the current node is marked as a word-end, you have found the shortest root — use it and stop. If you fall off the trie (character not found), no root exists for this word, so keep the original. This is the key advantage over a hash set approach: you never scan past the shortest match.
Reassemble the sentence from replaced words
Collect the replaced (or unchanged) words into a list and join with spaces. The total work is O(total characters in the sentence) for the trie lookups, plus O(total characters in dictionary) for construction. Space is O(dictionary size) for the trie nodes.
Solution
Common pitfalls
Not stopping at the first (shortest) root
for ch in word:
node = node[ch]
if '#' in node:
root = longest_so_farfor ch in word:
node = node[ch]
if '#' in node:
return current_prefixContinuing past the first word-end flag finds longer roots instead of shorter ones. The problem asks for the shortest root, so you must stop at the first hit.
Forgetting to check for missing characters in the trie
for ch in word:
node = node[ch]for ch in word:
if ch not in node:
break
node = node[ch]If a character is not in the trie, the word has no root. Without the check you get a KeyError (dict) or NoneType error (object). The word should be kept as-is.
Splitting and rejoining without preserving single spaces
return ' '.join(sentence.split(' '))return ' '.join(sentence.split())
Using split(' ') on a sentence with multiple consecutive spaces creates empty strings in the list, which join preserves as extra spaces. split() handles any whitespace and produces clean tokens.
Edge cases
The trie walk falls off without hitting a word-end flag. The original word is kept unchanged — no special branch needed.
cat and word catThe trie walk reaches the end of the root and sees the word-end flag, so it replaces the word with itself. Functionally a no-op, correctly handled.
a and ap for word appleThe trie walk hits the shorter root a first and returns immediately. The longer root is never reached.