Note: this problem is now LeetCode Premium-locked, so the statement above is reconstructed from the well-known public problem (a longstanding interview classic) rather than fetched live.
Each adjacent pair of words gives one piece of ordering information: scan for the first position where the two words differ, and the alphabet must place that earlier letter before the later one. This turns βderive the letter orderβ into βfind a topological order of a graph where an edge u β v means u comes before v.β If the ordering constraints are contradictory, the graph has a cycle and no valid order exists.
Two subtleties make this harder than a plain topo-sort: (1) a word that is a strict prefix of another (like "abc" before "ab") can never be valid regardless of letter order β return "" immediately; (2) letters that never appear in any differing pair still need to appear somewhere in the output, in any position.
Build the Graph, Then Kahn's BFS Topological Sort
OptimalTime O(C)Space O(1)Where C is the total number of characters across all words (bounded by 100 Γ 100 = 10^4 here, and by 26 possible letters/edges once graphs are built β more on that below).
- Initialize a node (and in-degree 0) for every distinct letter appearing in
words. - For each adjacent pair of words, find the first differing character position. If none exists and the first word is longer, the order is invalid (
""). Otherwise add a directed edgefirst-diff-letter-of-w1 β first-diff-letter-of-w2β but only if that edge hasnβt been added before (to avoid double-counting in-degree). - Run Kahnβs algorithm: repeatedly pop nodes with in-degree 0, append to the result, and decrement in-degree of their neighbors.
- If the result doesnβt include every letter, there was a cycle β return
"".
from collections import deque
class Solution: def alienOrder(self, words: list[str]) -> str: adj = {c: set() for word in words for c in word} indegree = {c: 0 for c in adj}
for w1, w2 in zip(words, words[1:]): min_len = min(len(w1), len(w2)) if len(w1) > len(w2) and w1[:min_len] == w2[:min_len]: return "" # "abc" before "ab" is impossible in any order for c1, c2 in zip(w1, w2): if c1 != c2: if c2 not in adj[c1]: adj[c1].add(c2) indegree[c2] += 1 break
queue = deque([c for c in indegree if indegree[c] == 0]) order = [] while queue: c = queue.popleft() order.append(c) for nxt in adj[c]: indegree[nxt] -= 1 if indegree[nxt] == 0: queue.append(nxt)
return "".join(order) if len(order) == len(indegree) else ""Worked example β words = ["wrt","wrf","er","ett","rftt"].
The four comparisons yield these edges: wrt/wrf β t β f, wrf/er β w β e, er/ett β r β t, ett/rftt β e β r β a single chain w β e β r β t β f, so the only valid order is forced.
Watch two things in the trace: how each edge is born β the comparison panel scans a pair left to right, dims the matching prefix, and stops at the first difference (amber), which is read straight off as an edge; and how Kahn consumes the graph β each scheduled letter drops a neighborβs in-degree badge to 0, and that neighbor slides into the ready queue.
The graph the comparisons are building
The graph starts empty: the 5 distinct letters become nodes, each with in-degree 0. Because `words` is sorted by the unknown alien alphabet, each adjacent pair `(words[i], words[i+1])` carries one ordering fact β the next steps extract it by scanning left to right for the first differing letter.
All 5 letters are scheduled on the final step, which means no cycle β so "wertf" is returned, matching the expected output.
Why itβs correct: this is standard topological sort correctness β a directed edge u β v means u must precede v in any valid ordering, and Kahnβs algorithm produces a valid linearization respecting all such constraints whenever one exists (equivalently, whenever the graph is a DAG). If some letters are never popped (in-degree never reaches 0), the remaining subgraph has a cycle, which is a direct contradiction in the alphabetβs ordering.
Complexity: let C be the total length of all words. Scanning adjacent word pairs to build edges is O(C) (each pairβs comparison is bounded by the shorter wordβs length). Because there are at most 26 letters, the graph has at most 26 nodes and 26 Γ 25 possible edges β a constant β so the BFS itself is O(1) in terms of the alphabet, and the dominant cost is the O(C) edge-extraction pass. Space is O(1) for the graph (bounded by the fixed alphabet size) plus O(C) implicitly for reading the input.
DFS Post-Order Topological Sort (Alternative)
OptimalTime O(C)Space O(1)The same edge set can be linearized with DFS instead of Kahnβs BFS: run DFS from every unvisited letter, and on the way back up the call stack (post-order), append the letter to the result. Reversing that post-order list gives a valid topological order β the same idea used in Reconstruct Itineraryβs Hierholzer traversal, and in Course Schedule II. Cycles are detected with a three-color scheme: WHITE (unvisited), GRAY (on the current DFS stack), BLACK (fully processed) β revisiting a GRAY node means a back-edge, i.e. a cycle.
class Solution: def alienOrder(self, words: list[str]) -> str: adj = {c: set() for word in words for c in word}
for w1, w2 in zip(words, words[1:]): min_len = min(len(w1), len(w2)) if len(w1) > len(w2) and w1[:min_len] == w2[:min_len]: return "" for c1, c2 in zip(w1, w2): if c1 != c2: adj[c1].add(c2) break
WHITE, GRAY, BLACK = 0, 1, 2 color = {c: WHITE for c in adj} order = []
def dfs(c: str) -> bool: color[c] = GRAY for nxt in adj[c]: if color[nxt] == GRAY: return False # cycle: back-edge to a node on the current path if color[nxt] == WHITE and not dfs(nxt): return False color[c] = BLACK order.append(c) return True
for c in adj: if color[c] == WHITE and not dfs(c): return ""
return "".join(reversed(order))Why post-order-then-reverse works: a letter is only appended to order after all letters reachable from it have already been appended. So in the final list, every letter appears before anything it points to only once reversed β i.e. reversing puts each u before its dependents v for every edge u β v, exactly the topological property required.
Complexity: identical to the BFS version β O(C) time to build the edges (bounded alphabet-size DFS afterward), O(1) / O(26) graph space. This variant is a matter of taste (iterative BFS vs. recursive DFS); Kahnβs is often preferred in interviews since it avoids recursion depth concerns, though with only 26 possible nodes here thatβs not a real risk.