LeetCode #720 Medium

Longest Word in Dictionary

Longest Word in Dictionary is LeetCode 720 (Medium). You get an array words of lowercase English strings. Find the longest word that can be built one letter at a time from other words in the array.

  • A word of length k is buildable when its prefixes of length 1, 2, …, k − 1 are all in words. Every step must add exactly one letter at the end.
  • A one-letter word is always buildable: it has no shorter prefix to check.
  • If several buildable words share the maximum length, return the one that comes first in lexicographical order.
  • If no word is buildable, return the empty string.

There are at most 1,000 words of at most 30 letters, so checking every prefix of every word is cheap; the interest is in doing it cleanly and getting the tie rule right.

Constraints
  • 1 <= words.length <= 1000
  • 1 <= words[i].length <= 30
  • words[i] consists of lowercase English letters.
triestringssorting
Open on LeetCode ↗
02

Intuition

Put every word into a trie and mark the node where each word ends. A root-to-node path spells a prefix, so "every prefix is a word" becomes visible: every node on the path ends a word.

The problem turns into a walk that only steps into word-ending children. Every node it reaches is buildable, and anything behind a non-word node has a missing prefix, so the deepest node reached is the answer.

Trying children in alphabetical order settles ties for free: the walk meets words in dictionary order, so the first word found at the maximum length is the smallest one.

How to spot this pattern

"Every prefix must also be present" is the signal for a trie, because a trie stores all prefixes of a word along one path. The same walk appears in Longest Word With All Prefixes (LeetCode 1858), and the trie itself is Implement Trie (LeetCode 208). The longest word in dictionary LeetCode problem is the gentlest of these: one insert pass, one restricted DFS.

03

Approach

Try it first

Before reading on: with words = ["b", "ba", "bad", "a", "ab", "abd"], which buildable words have the maximum length, and which one should be returned? Then decide whether you update the answer on > or on >= if you scan candidates in alphabetical order.

1

Build the trie

Insert each word letter by letter, creating child nodes as needed, and store the word itself at its last node. That stored word marks a word-ending node, and it saves rebuilding the string from the path later. The root stores nothing.

2

Walk only through word-ending children

From the root, visit children in order a to z and skip any child that does not end a word. Every word below a skipped node would need that node as one of its steps, so the whole subtree is pruned without being looked at.

3

Keep the longest, earliest word

At each visited node, replace best only when its word is strictly longer, then continue into its children. Because the walk reaches words alphabetically, the first word of each length is already the smallest, and a strict > never lets a later one replace it.

4

Return the result

After the walk, return best. It starts as "", so when no one-letter word exists the root has no word-ending child, nothing is visited, and the empty string comes back without a special case.

5

Why the walk is correct

A node is visited exactly when every node on its path ends a word, which is the definition of buildable. Visiting children alphabetically is a pre-order traversal, so words of equal length are met in lexicographic order and the first one is kept.

04

Longest Word in Dictionary solution in Python | C++ | Java

▶1class Solution:
▶2 def longestWord(self, words: List[str]) -> str:
▶3 root = {}
▶4 for word in words:
▶5 node = root
▶6 for ch in word:
▶7 node = node.setdefault(ch, {})
▶8 node["$"] = word
▶9 best = ""
▶10 
▶11 def dfs(node):
▶12 nonlocal best
▶13 for ch in sorted(k for k in node if k != "$"):
▶14 child = node[ch]
▶15 if "$" not in child:
▶16 continue
▶17 if len(child["$"]) > len(best):
▶18 best = child["$"]
▶19 dfs(child)
▶20 
▶21 dfs(root)
▶22 return best
rootwinsertwlength 1bestemptyinsert word 1 of 5
word"w"length 1
new nodes1created
w is new, so its single node is created under the root. The last node gets a solid ring: a word ends there. Dashed rings are prefixes that are not words themselves, and those are what will block the walk.
rootwoinsertwolength 2bestemptyinsert word 2 of 5
word"wo"length 2
new nodes1created
The prefix w is already a path, so only 1 new node is added. Its last node gets a solid ring.
rootworinsertworlength 3bestemptyinsert word 3 of 5
word"wor"length 3
new nodes1created
The prefix wo is already a path, so only 1 new node is added. Its last node gets a solid ring.
rootworlinsertworllength 4bestemptyinsert word 4 of 5
word"worl"length 4
new nodes1created
The prefix wor is already a path, so only 1 new node is added. Its last node gets a solid ring.
rootworldinsertworldlength 5bestemptyinsert word 5 of 5
word"world"length 5
new nodes1created
The prefix worl is already a path, so only 1 new node is added. Its last node gets a solid ring.
rootworldnodewlength 1bestwlength 1new best: length 1
node"w"a word, buildable
best"w"was ""
w is a word, and every node above it was a word too, so it can be built one letter at a time. It is longer than the empty string, so it becomes the best. Keep going down from here.
rootworldnodewolength 2bestwolength 2new best: length 2
node"wo"a word, buildable
best"wo"was "w"
wo is a word, and every node above it was a word too, so it can be built one letter at a time. It is longer than w, so it becomes the best. Keep going down from here.
rootworldnodeworlength 3bestworlength 3new best: length 3
node"wor"a word, buildable
best"wor"was "wo"
wor is a word, and every node above it was a word too, so it can be built one letter at a time. It is longer than wo, so it becomes the best. Keep going down from here.
rootworldnodeworllength 4bestworllength 4new best: length 4
node"worl"a word, buildable
best"worl"was "wor"
worl is a word, and every node above it was a word too, so it can be built one letter at a time. It is longer than wor, so it becomes the best. Keep going down from here.
rootworldnodeworldlength 5bestworldlength 5new best: length 5
node"world"a word, buildable
best"world"was "worl"
world is a word, and every node above it was a word too, so it can be built one letter at a time. It is longer than worl, so it becomes the best. It has no children, so the walk backs up.
rootworldbestworldlength 5return "world"
result"world"length 5
reached5of 5 words
Done. Every node reachable through word-ending nodes has been visited. Every word was reachable here. The deepest node reached, world, is the answer.
05

Sort + hash set

Sorting puts every word after its own prefixes, so a single pass can test word[:-1] against the set of words already proven buildable. In longest word in dictionary Python code this is the shortest version.

▶1class Solution:
▶2 def longestWord(self, words: List[str]) -> str:
▶3 words.sort()
▶4 built = {""}
▶5 best = ""
▶6 for word in words:
▶7 if word[:-1] in built:
▶8 built.add(word)
▶9 if len(word) > len(best):
▶10 best = word
▶11 return best
06

Common pitfalls

Checking only the one-letter-shorter prefix

✗ Wrong
if word[:-1] in words_set:
    candidates.append(word)
✓ Right
if word[:-1] in built:
    built.add(word)

With ["ab", "abc"], "abc" passes because "ab" is in the list, yet "a" is not, so neither word is buildable. The prefix has to be buildable itself, so test against the set of words already proven buildable, processed in sorted order.

Updating the answer on >=

✗ Wrong
if len(child["$"]) >= len(best):
    best = child["$"]
✓ Right
if len(child["$"]) > len(best):
    best = child["$"]

The walk meets words in alphabetical order, so the first word of the maximum length is the right one. >= replaces it with every later word of the same length and returns the largest instead, for example "apply" instead of "apple".

Visiting children in insertion order

✗ Wrong
for ch in node:
    ...
✓ Right
for ch in sorted(k for k in node if k != "$"):
    ...

A Python dict iterates in insertion order, which follows the input, not the alphabet. The tie rule then depends on how the words were listed. C++ and Java arrays indexed by ch - 'a' are already in order.

07

Edge cases

No one-letter word

The root has no word-ending child, so the walk visits nothing and the answer is "", even if long words exist.

08

Complexity

Time
O(L)
Space
O(L)
L is the total number of letters across all words (at most 30,000 here). Insertion touches each letter once and the walk visits each trie node at most once. The trie has at most L nodes. Sorting each node's keys adds a factor of at most 26 log 26, a constant.
09

Three "longest word" problems that are easy to mix up

They share a name pattern but test different things.

ProblemWord mustTie-breakUsual tool
Longest Word in Dictionary (720)have every prefix in the listlexicographically smallesttrie or sort + set
Longest Word With All Prefixes (1858)have every prefix in the list (premium variant)lexicographically smallesttrie
Longest Word in Dictionary through Deleting (524)be a subsequence of a given string slexicographically smallesttwo pointers per word
10

Longest Word in Dictionary FAQ

Why does DFS in alphabetical order give the lexicographically smallest answer?

A pre-order walk that tries children a to z lists the words of a trie in dictionary order. Among words of the maximum length the walk therefore reaches the smallest first, and a strict > comparison never lets a later word of equal length replace it.

What are the time and space complexity of LeetCode 720?

O(L) time and space for the trie solution, where L is the total length of all words.