269. Alien Dictionary - Explanation
Description
There is a new alien language that uses the English alphabet, but the order of the letters is unknown.
You are given a list of strings words from the alien language's dictionary. It is claimed that the strings in words are sorted lexicographically by the rules of this new language.
If this claim is incorrect, and the given arrangement of strings in words cannot correspond to any order of letters, return "".
Otherwise, return a string of the unique letters in the new alien language sorted in lexicographically increasing order by the new language's rules. If there are multiple solutions, return any of them.
A string a is lexicographically smaller than a string b if either of the following is true:
- The first letter where they differ is smaller in
athan inb. ais a prefix ofbanda.length < b.length.
Example 1:
Input: words = ["z","o"]
Output: "zo"Explanation:
From "z" and "o", we know 'z' < 'o', so return "zo".
Example 2:
Input: words = ["hrn","hrf","er","enn","rfnn"]
Output: "hernf"Explanation:
- from
"hrn"and"hrf", we know'n' < 'f' - from
"hrf"and"er", we know'h' < 'e' - from
"er"and"enn", we know'r' < 'n' - from
"enn"and"rfnn"we know'e' < 'r' - so one possible solution is
"hernf"
Example 3:
Input: words = ["abc","ab"]
Output: ""Explanation:
The second word is a prefix of the first word, but the first word appears before the second. This is impossible in a valid lexicographical ordering, so return "".
Constraints:
1 <= words.length <= 1001 <= words[i].length <= 100words[i]consists of only lowercase English letters.
Topics
Recommended Time & Space Complexity
You should aim for a solution with O(N + V + E) time and O(V + E) space, where N is the sum of the lengths of all the strings, V is the number of unique characters (vertices), and E is the number of edges.
Hint 1
Can you think of this as a graph problem? Characters from a through z are nodes. What could the edges represent here? How can you create edges from the given words? Perhaps you should try comparing two adjacent words.
Hint 2
The relative ordering of the characters can be treated as edges. For example, consider the words ordered as ["ape", "apple"]. "ape" comes before "apple", which indicates that 'e' is a predecessor of 'p'. Therefore, there is a directed edge e -> p, and this dependency should be valid across all the words. In this way, we can build an adjacency list by comparing adjacent words. Can you think of an algorithm that is suitable to find a valid ordering?
Hint 3
We can use Topological Sort to ensure every node appears after its predecessor. Using DFS, we traverse the graph built from the adjacency list. A visited map tracks nodes in the current DFS path: False means not in the path, and True means in the path. If any DFS call returns True, it indicates a cycle and we return immediately. How do we extract the ordering from this DFS?
Hint 4
When we visit a node and its children and don't find a cycle, we mark the node as False in the map and append it to the result, treating this as a post-order traversal. If we find a cycle, we return an empty string; otherwise, we return the result list.
Prerequisites
Before attempting this problem, you should be comfortable with:
- Graph Representation - Building adjacency lists from relationships between elements
- Topological Sort - Ordering nodes in a directed acyclic graph based on dependencies
- Depth First Search (DFS) - Recursive graph traversal with cycle detection using visited states
- Kahn's Algorithm (BFS) - Iterative topological sort using in-degree counting
- String Comparison - Extracting ordering rules by comparing adjacent strings character by character
1. Depth First Search
Intuition
The words are already sorted in an unknown alphabet order.
So when you compare two adjacent words, the first position where they differ tells you a rule about letter order:
- If
w1[j] != w2[j], thenw1[j]must come beforew2[j]in the alien alphabet (w1[j]->w2[j]).
All these rules form a directed graph (letters = nodes, “comes before” = directed edge).
Now the problem becomes: find a topological ordering of this graph.
DFS helps in two ways:
- Build the ordering (postorder append).
- Detect cycles (if there’s a cycle, no valid alphabet exists).
Also, special invalid case:
- If
w1is longer butw2is a prefix ofw1(like"abc"before"ab"), that's impossible - return"".
Algorithm
- Build a graph with every unique character as a node.
- For each pair of adjacent words:
- If
w1starts withw2andlen(w1) > len(w2), return"". - Otherwise, find the first differing character and add edge
w1[j]->w2[j].
- If
- Run
dfsfrom every character:- Use 3-state tracking (commonly done with a map):
- visiting (in current recursion path) - cycle if seen again
- visited (fully processed) - skip
- unvisited
- After exploring all neighbors, add the character to result (postorder).
- Use 3-state tracking (commonly done with a map):
- Reverse the result list to get the alien alphabet order.
- If any
dfsfinds a cycle, return""; else return the joined string.
class Solution:
def foreignDictionary(self, words: List[str]) -> str:
adj = {c: set() for w in words for c in w}
for i in range(len(words) - 1):
w1, w2 = words[i], words[i + 1]
minLen = min(len(w1), len(w2))
if len(w1) > len(w2) and w1[:minLen] == w2[:minLen]:
return ""
for j in range(minLen):
if w1[j] != w2[j]:
adj[w1[j]].add(w2[j])
break
visited = {}
res = []
def dfs(char):
if char in visited:
return visited[char]
visited[char] = True
for neighChar in adj[char]:
if dfs(neighChar):
return True
visited[char] = False
res.append(char)
for char in adj:
if dfs(char):
return ""
res.reverse()
return "".join(res)Time & Space Complexity
- Time complexity:
- Space complexity:
Where is the number of unique characters, is the number of edges and is the sum of lengths of all the strings.
2. Topological Sort (Kahn's Algorithm)
Intuition
From the sorted alien words, each pair of adjacent words gives you a letter-order rule at the first mismatching character:
- if
w1[j] != w2[j], thenw1[j]->w2[j](meaningw1[j]comes beforew2[j]).
These rules form a directed graph. The alien alphabet is just a topological ordering of this graph.
Kahn's algorithm (BFS topological sort) works by:
- Counting how many prerequisites each letter has (
indegree). - Always picking letters with
indegree = 0(no unmet prerequisites) and "removing" them from the graph.
If there's a cycle, some letters will never reach indegree 0, so we won't be able to output all letters.
Also invalid input case:
- If a longer word comes before its own prefix (e.g.,
"abc"before"ab"), ordering is impossible.
Algorithm
- Create a graph node for every unique character in all words.
- For each adjacent pair
(w1, w2):- If
w1starts withw2andlen(w1) > len(w2), return"". - Find the first index
jwhere they differ and add edgew1[j]->w2[j](only once). - Increase
indegree[w2[j]]when you add a new edge.
- If
- Push all characters with
indegree = 0into a queue. - While the queue is not empty:
- Pop a character, add it to the answer.
- For each neighbor, decrement its
indegree. - If a neighbor becomes
0, push it into the queue.
- If the answer contains fewer characters than total unique characters, a cycle exists - return
"". - Otherwise, join the answer list and return it.
class Solution:
def foreignDictionary(self, words):
adj = {c: set() for w in words for c in w}
indegree = {c: 0 for c in adj}
for i in range(len(words) - 1):
w1, w2 = words[i], words[i + 1]
minLen = min(len(w1), len(w2))
if len(w1) > len(w2) and w1[:minLen] == w2[:minLen]:
return ""
for j in range(minLen):
if w1[j] != w2[j]:
if w2[j] not in adj[w1[j]]:
adj[w1[j]].add(w2[j])
indegree[w2[j]] += 1
break
q = deque([c for c in indegree if indegree[c] == 0])
res = []
while q:
char = q.popleft()
res.append(char)
for neighbor in adj[char]:
indegree[neighbor] -= 1
if indegree[neighbor] == 0:
q.append(neighbor)
if len(res) != len(indegree):
return ""
return "".join(res)Time & Space Complexity
- Time complexity:
- Space complexity:
Where is the number of unique characters, is the number of edges and is the sum of lengths of all the strings.
Common Pitfalls
Missing the Invalid Prefix Case
When a longer word appears before its own prefix (e.g., "abc" before "ab"), this is an invalid ordering that cannot exist in any alphabet. Failing to detect this case and return an empty string leads to incorrect results or undefined behavior.
Only Comparing Adjacent Words Partially
The ordering information comes from the first differing character between adjacent words. A common mistake is comparing all differing characters or only comparing the first characters. You must find the first position where characters differ and extract exactly one edge from that comparison.
Not Including All Characters in the Result
Characters that appear in the words but have no ordering constraints (no incoming or outgoing edges) must still appear in the final alphabet. Forgetting to initialize nodes for all unique characters causes some letters to be missing from the output.
Incorrect Cycle Detection in DFS
The three-state visited tracking (unvisited, visiting, visited) is crucial for detecting cycles. Using only two states (visited/unvisited) cannot distinguish between a back edge (cycle) and a cross edge (already processed node). This leads to either false cycle detection or missing actual cycles.
Adding Duplicate Edges
When the same character pair appears from multiple adjacent word comparisons, adding duplicate edges inflates the indegree count in Kahn's algorithm, preventing nodes from ever reaching zero indegree. Use a set to track existing edges before adding new ones.
Sign in to join the discussion