Example 1
ready- Input
words = [ "wrt", "wrf", "er", "ett", "rftt" ]- Expected
"wertf"- Why
- the first differences force w < e < r < t < f
A book importer receives a catalog that's already alphabetized, but the alphabet key is missing. The order of the words is the only evidence you get.
You're given words, a list that claims to be sorted by an unknown ordering of
lowercase English letters. Lexicographic order means dictionary-style order: the
first different letter decides which word comes first, while a shorter word comes first
when one word is an exact prefix of the other.
Recover the alien alphabet for every letter that appears. Return one string containing each appearing letter exactly once, from earliest to latest.
A few ground rules:
"" if the clues contain a cycle — a loop of requirements such as
a before b and b before a."" when a longer word appears before its own prefix. A catalog that
places "signal" before "sign" can't be sorted under any alphabet.1 <= words.length <= 1001 <= words[i].length <= 20k <= 10.Hints
Compare each word with the one immediately after it. At their first different position, the letter from the earlier word must come before the letter from the later word. Characters after that position reveal nothing.
If every character matches through the shorter word, check the lengths. Shorter before longer is fine; longer before shorter is the invalid-prefix case.
Build a directed graph — letters are nodes and each arrow records "must come before." A topological sort is a listing that places every arrow's source before its destination. Kahn's algorithm builds one by repeatedly taking a letter whose indegree, the number of incoming arrows, is zero. Finishing early exposes a cycle.
How would you report that several alphabets fit the same words? In Kahn's algorithm, what does having two zero-indegree choices at once tell you?
Visible cases
words = [
"wrt",
"wrf",
"er",
"ett",
"rftt"
]"wertf"words = [
"z",
"x"
]"zx"words = [
"abc",
"ab"
]""Interview signal
Two neighboring words can hide one sharp clue. If "mira" appears before
"miso", their shared "mi" tells you nothing; the first mismatch tells
you r must come before s. Every character after that mismatch is
irrelevant to this comparison.
The reusable pattern is: build a letter-precedence graph — a directed graph where an
edge a → b means a must appear before b — from adjacent word
pairs, then produce a topological sort, an ordering that respects every edge. The
optimal traversal below is Kahn's algorithm.
Before either traversal, build the same graph:
"".Let C be the total number of characters, k the number of letters, and
e the number of distinct precedence edges.
Depth-first search (DFS) follows one chain of edges as far as it can before returning.
Mark a letter visiting when its search starts and done when it ends.
Reaching a visiting letter again proves that the clues contain a cycle.
Append each letter only after all letters that follow it are done. That creates the order backwards, so reverse it once at the end.
def alien_order(words: list[str]) -> str:
graph: dict[str, set[str]] = {
letter: set()
for word in words
for letter in word
}
for before, after in zip(words, words[1:]):
limit = min(len(before), len(after))
index = 0
while index < limit and before[index] == after[index]:
index += 1
if index == limit:
if len(before) > len(after):
return ""
continue
graph[before[index]].add(after[index])
state = {letter: 0 for letter in graph} # 0=new, 1=visiting, 2=done
reverse_order: list[str] = []
def visit(letter: str) -> bool:
if state[letter] == 1:
return False
if state[letter] == 2:
return True
state[letter] = 1
for next_letter in graph[letter]:
if not visit(next_letter):
return False
state[letter] = 2
reverse_order.append(letter)
return True
for letter in graph:
if not visit(letter):
return ""
reverse_order.reverse()
return "".join(reverse_order)Time O(C), space O(k + e) — every adjacent comparison, node, and distinct edge is
processed once. The recursion stack holds at most k letters.
Kahn's algorithm tracks each node's indegree, the number of edges pointing into it. A zero-indegree letter has no unmet requirement, so it can safely be placed next. Keep ready letters in a queue, a first-in, first-out line. Remove a chosen letter's outgoing edges; that may make more letters ready.
from collections import deque
def alien_order(words: list[str]) -> str:
graph: dict[str, set[str]] = {
letter: set()
for word in words
for letter in word
}
indegree = {letter: 0 for letter in graph}
for before, after in zip(words, words[1:]):
limit = min(len(before), len(after))
index = 0
while index < limit and before[index] == after[index]:
index += 1
if index == limit:
if len(before) > len(after):
return ""
continue
earlier = before[index]
later = after[index]
if later not in graph[earlier]:
graph[earlier].add(later)
indegree[later] += 1
ready = deque(
letter
for letter in graph
if indegree[letter] == 0
)
order: list[str] = []
while ready:
letter = ready.popleft()
order.append(letter)
for next_letter in graph[letter]:
indegree[next_letter] -= 1
if indegree[next_letter] == 0:
ready.append(next_letter)
if len(order) != len(graph):
return ""
return "".join(order)Every letter you append has no remaining predecessor, so appending it can't violate an
edge. If the queue empties before all k letters are placed, every remaining
letter is trapped behind another remaining letter; following those requirements
eventually closes a cycle.
Time O(C), space O(k + e) — graph construction reads the input once, and Kahn's traversal removes each node and edge once.
Every valid input here has one forced order, so Kahn's queue never presents a real tie. The durable move is the pattern itself: adjacent first differences become graph edges, and topological sorting turns those local clues into one global order.
Ready to run.
3 cases are queued.
Visible testcase
Case 1
Expected
"wertf"