Alien Dictionary

Hard Graphs

Problem

Given a list of words sorted lexicographically according to an unknown alien language, return a string representing the characters in the alien language sorted in lexicographical order. Return an empty string if no valid ordering exists.

  • 1 ≤ words.length ≤ 100
  • 1 ≤ words[i].length ≤ 100
  • words[i] consists of only lowercase English letters

Example

Input: words = ["wrt", "wrf", "er", "ett", "rftt"]
Output: "wertf"

The algorithm compares adjacent words to infer ordering constraints between characters. For example, comparing "wrt" and "wrf" reveals that 't' comes before 'f'. Comparing "wrf" and "er" shows 'w' comes before 'e'. These pairwise constraints build a directed graph representing character precedence. The critical moment is detecting an invalid prefix case: if a longer word precedes a shorter word that is a prefix of it (e.g., "abc" before "ab"), the ordering is invalid, and the algorithm returns an empty string immediately.

Approach

Straightforward Solution

A naive approach might attempt to guess character orderings or try all permutations, which is infeasible due to combinatorial explosion.

Core Observation

The lexicographical order of words implies a partial order of characters. Comparing adjacent words reveals direct precedence relations between characters, which can be represented as edges in a directed graph.

Path to Optimal

The key insight is to model the problem as a graph of character precedence and perform a topological sort to find a valid linear ordering. Detecting invalid prefix cases early prevents futile computation. Constructing the graph by comparing adjacent words and extracting the first differing character pair yields the minimal necessary edges. The topological sort then detects cycles, which indicate contradictory orderings.

Optimal Approach

Build a directed graph where nodes are characters and edges represent precedence constraints derived from adjacent word comparisons. Detect invalid prefix cases by checking if a longer word precedes its prefix. Use DFS-based topological sort with cycle detection to produce a valid character order or return empty string if a cycle exists.

Time

O(C + V)

Building the graph requires comparing adjacent words and their characters, which takes O(C) where C is the total number of characters across all words. The DFS topological sort visits each node and edge once, resulting in O(V + E) time, where V is the number of unique characters and E is the number of edges. Since E ≤ C, total complexity is O(C + V).

Space

O(V + E)

The adjacency list stores edges for each character, requiring O(V + E) space. The recursion stack and visited dictionary use O(V) space. This auxiliary space is necessary to represent the graph and track traversal state.

Pattern Spotlight

Topological Sort (Cycle Detection in Directed Graph)

When inferring order from pairwise precedence constraints, represent characters as graph nodes and edges as 'comes before' relations; then use DFS with cycle detection to find a valid linear order or detect contradictions.

Solution

1class Solution:
2 def alienOrder(self, words: list[str]) -> str:
3 adj = { c: set() for w in words for c in w }
4
5 for i in range(len(words) - 1):
6 w1, w2 = words[i], words[i + 1]
7 min_len = min(len(w1), len(w2))
8 if len(w1) > len(w2) and w1[:min_len] == w2[:min_len]:
9 return ""
10 for j in range(min_len):
11 if w1[j] != w2[j]:
12 adj[w1[j]].add(w2[j])
13 break
14
15 visit = {}
16 res = []
17
18 def dfs(c):
19 if c in visit:
20 return visit[c]
21
22 visit[c] = True
23 for nei in adj[c]:
24 if dfs(nei):
25 return True
26
27 visit[c] = False
28 res.append(c)
29
30 for c in adj:
31 if dfs(c):
32 return ""
33
34 return "".join(res[::-1])

Step-by-Step Solution

1

Construct Directed Graph of Character Precedence from Adjacent Words

3adj = { c: set() for w in words for c in w }
5for i in range(len(words) - 1):
6 w1, w2 = words[i], words[i + 1]
7 min_len = min(len(w1), len(w2))
8 if len(w1) > len(w2) and w1[:min_len] == w2[:min_len]:
9 return ""
10 for j in range(min_len):
11 if w1[j] != w2[j]:
12 adj[w1[j]].add(w2[j])
13 break

Objective

To build a graph where each character points to characters that must come after it, based on the first differing character between adjacent words.

Key Insight

Comparing adjacent words reveals the minimal necessary ordering constraints. The first position where two words differ determines a direct precedence edge from the character in the first word to the character in the second. This approach avoids over-constraining the graph and ensures only valid edges are added. Detecting the invalid prefix case here prevents futile graph construction.

Interview Quick-Check

Core Logic

Build adjacency sets for each character by comparing adjacent words and adding an edge from the first differing character in the first word to that in the second.

Common Pitfalls & Bugs

Failing to detect the invalid prefix case where a longer word precedes its prefix leads to incorrect results or infinite loops.

State & Boundaries

Stop comparing characters at the first difference to avoid adding incorrect edges.

2

Perform DFS-Based Topological Sort with Cycle Detection

15visit = {}
16res = []
18def dfs(c):
19 if c in visit:
20 return visit[c]
22 visit[c] = True
23 for nei in adj[c]:
24 if dfs(nei):
25 return True
27 visit[c] = False
28 res.append(c)
30for c in adj:
31 if dfs(c):
32 return ""
34return "".join(res[::-1])

Objective

To traverse the graph depth-first, detect cycles, and build the character order in reverse postorder.

Key Insight

DFS traversal marks nodes as visiting or visited to detect cycles: encountering a node marked visiting indicates a cycle and invalid ordering. Appending characters to the result list after exploring all neighbors ensures a valid topological order when reversed. This approach efficiently detects contradictions and produces the correct order or returns empty string if impossible.

Interview Quick-Check

Core Logic

Use DFS with a visit state dictionary to detect cycles and append characters post-recursion to build a valid topological order.

State & Boundaries

Mark nodes as visiting (True) when recursion starts and visited (False) when recursion ends to track traversal state.

Common Pitfalls & Bugs

Not reversing the result list before returning leads to incorrect character order.

Line Analysis

This solution has 12 Critical lines interviewers watch for.

Line 8 Critical
if len(w1) > len(w2) and w1[:min_len] == w2[:min_len]:

Check for invalid prefix case where longer word precedes its prefix.

This condition violates lexicographical order rules, so detecting it early allows immediate termination with an empty string, preventing incorrect results.

Line 31 Critical
if dfs(c):

If DFS detects a cycle starting from this character, return empty string.

Cycle detection invalidates the ordering, so the algorithm must terminate early with no solution.

Line 9 Critical
return ""

Return empty string upon detecting invalid prefix ordering.

Returning immediately signals no valid character order exists due to contradictory input ordering.

Line 11 Critical
if w1[j] != w2[j]:

Check if characters at current position differ.

Identifying the first differing character pair is critical to establishing a precedence edge in the graph.

Line 12 Critical
adj[w1[j]].add(w2[j])

Add an edge from the character in the first word to the character in the second word.

This edge encodes the ordering constraint that the first character must come before the second in the alien language.

Line 19 Critical
if c in visit:

Return visit state if character has been visited or is currently visiting.

This line prevents redundant work and detects cycles by returning True if a cycle is found (node is currently visiting).

Line 20 Critical
return visit[c]

Mark character as currently visiting (True).

Marking as visiting signals the start of exploration; encountering this state again indicates a cycle.

Line 23 Critical
for nei in adj[c]:

If a cycle is detected in any neighbor, propagate True to indicate failure.

Early termination upon cycle detection prevents unnecessary traversal and signals invalid ordering.

Line 24 Critical
if dfs(nei):

Return True immediately if a cycle is found.

This return propagates cycle detection up the recursion stack, aborting the topological sort.

Line 28 Critical
res.append(c)

Append character to result list after visiting all neighbors.

Appending post-recursion ensures characters appear after all their dependencies, building a valid topological order in reverse.

Line 32 Critical
return ""

Return empty string upon cycle detection.

Signals to the caller that no valid alien dictionary ordering exists due to contradictory constraints.

Line 34 Critical
return "".join(res[::-1])

Return the reversed result list as a string representing the alien dictionary order.

Reversing the postorder traversal list produces the correct topological order of characters consistent with all precedence constraints.

Line 3 Core
adj = { c: set() for w in words for c in w }

Initialize adjacency list mapping each character to an empty set of neighbors.

Creating an adjacency set for every character ensures all characters appear as nodes in the graph, even if they have no outgoing edges, which is essential for a complete topological sort.

Line 10 Core
for j in range(min_len):

Iterate through characters up to the minimum length to find first differing character.

The first differing character between adjacent words determines a direct precedence edge; subsequent characters do not provide valid ordering information.

Line 15 Core
visit = {}

Initialize a dictionary to track visit state of characters during DFS.

Tracking visit state is essential for cycle detection and preventing redundant traversals during topological sort.

Line 18 Core
def dfs(c):

Define DFS function to traverse graph and detect cycles.

DFS enables depth-first exploration of dependencies, allowing cycle detection and ordering construction in one pass.

Line 22 Core
visit[c] = True

Recursively visit all neighbors of the current character.

Exploring neighbors enforces all precedence constraints and detects cycles in downstream nodes.

Line 27 Core
visit[c] = False

Mark character as fully visited (False).

Marking as visited indicates the node and its descendants have been processed without cycles, allowing reuse of this state.

Line 5 Core
for i in range(len(words) - 1):

Iterate through pairs of adjacent words to infer ordering constraints.

Comparing adjacent words is the only way to extract direct precedence relations between characters, as the input words are sorted lexicographically according to the alien language.

Line 13 Core
break

Break out of the loop after adding the first precedence edge.

Only the first differing character pair matters for ordering; further characters do not affect the relative order of these two words.

Line 16 Core
res = []

Initialize a list to accumulate the topological order of characters.

Characters are appended post-recursion to build the order in reverse; this list will be reversed before returning the final result.

Line 30 Core
for c in adj:

Iterate over all characters in the graph to initiate DFS.

Ensures all disconnected components and isolated nodes are processed for a complete ordering.

Line 7 Standard
min_len = min(len(w1), len(w2))

Determine the minimum length of the two words for safe comparison.

Comparisons beyond the shorter word's length are invalid and can cause index errors; this also helps detect invalid prefix cases.

Line 25 Standard
return True

End of neighbor iteration.

Completing neighbor exploration without cycle detection means current node is safe to finalize.

Line 6 Standard
w1, w2 = words[i], words[i + 1]

Assign the current and next words for comparison.

Storing the two words simplifies subsequent character-by-character comparison and prefix checks.

Test Your Understanding

Why must the algorithm detect the invalid prefix case where a longer word precedes its prefix, and how does this affect the ordering?

Because in lexicographical order, a prefix word must come before any longer word starting with that prefix. If a longer word appears before its prefix, no valid character ordering can satisfy this, so the algorithm must return an empty string immediately to indicate no valid ordering exists.

Related Problems

Graphs pattern

Don't just read it. Drill it.

Reconstruct Alien Dictionary from memory until it sticks. AlgoDrill blanks out key lines and makes you fill them back in, step by step.

Unlock the Alien Dictionary drill

or drill a free problem