Topics › Graphs › Topo Sort and Problems
Alien Dictionary
LeetCode ↗ Java source ↗ Edit this article ↗
Jump to code:
Problem
An alien language uses the first k letters of the English alphabet, but in an unknown order. You are given a list of N words that is sorted according to that alien order (sorted the way a dictionary is: compare letter by letter, and a word comes before any longer word it is a prefix of). Return a string containing each of the k letters exactly once, in an order consistent with the list. If several orders are consistent, return any of them. If no order can make the list sorted, return "".
Constraints: 1 ≤ N ≤ 300, 1 ≤ k ≤ 26, 1 ≤ word length ≤ 50, words use only the first k letters.
Example 1
words = ["baa", "abcd", "abca", "cab", "cad"], k = 4
answer = "bdac"
Example 2
words = ["caa", "aaa", "aab"], k = 3
answer = "cab" // c before a ("caa" < "aaa"), a before b ("aaa" < "aab")
The LeetCode version (premium) differs slightly: the alphabet is “every letter that appears in the words” rather than the first k letters. The same algorithm works if you track which letters are present and only emit those.
What a sorted list tells us. Compare two adjacent words. Scan until the first position where they differ. If the letters there are x in the upper word and y in the lower one, then x comes before y in the alien alphabet. Two other things are worth stating:
- Letters after the first difference say nothing. In English, “cat” < “dog” says nothing about
aversuso. - If there is no difference within the shorter length, one word is a prefix of the other. The shorter one must come first. If the longer word comes first (
"abc"above"ab"), no alphabet can explain the list.
Only adjacent pairs are needed: sortedness is transitive, so any constraint implied by two words further apart is already implied by the chain of adjacent pairs between them.
Approach 1: Brute force, try every alphabet
Intuition. The answer is one of the k! orderings of the letters. Generate them one by one and, for each, check whether the word list is sorted under that ranking. The first ordering that passes is a valid answer. It is hopeless for large k, but it is a correct reference and shows exactly what “consistent with the list” means.
Algorithm.
- Start with the letters
a, b, c, ...in an array. - Generate permutations by swapping each remaining letter into the current position and recursing.
- At a full permutation, compute each letter’s rank and check every adjacent word pair using the rules above.
- Return the first permutation that passes, or
""if none does.
static String bruteForce(String[] words, int k) {
char[] letters = new char[k];
for (int i = 0; i < k; i++) letters[i] = (char) ('a' + i);
String found = tryAll(letters, 0, words);
return found == null ? "" : found;
}
/** Swap-based permutation: fixes letters[0..pos-1] and tries every remaining letter at position pos. */
static String tryAll(char[] letters, int pos, String[] words) {
if (pos == letters.length) return isSortedUnder(words, letters) ? new String(letters) : null;
for (int i = pos; i < letters.length; i++) {
swap(letters, pos, i);
String found = tryAll(letters, pos + 1, words);
swap(letters, pos, i);
if (found != null) return found;
}
return null;
}
static void swap(char[] a, int i, int j) {
char t = a[i];
a[i] = a[j];
a[j] = t;
}
/** True if every adjacent pair of words is in non-decreasing order when letters rank as in order. */
static boolean isSortedUnder(String[] words, char[] order) {
int[] rank = new int[26];
for (int i = 0; i < order.length; i++) rank[order[i] - 'a'] = i;
for (int w = 0; w + 1 < words.length; w++) {
String a = words[w], b = words[w + 1];
int len = Math.min(a.length(), b.length()), i = 0;
while (i < len && a.charAt(i) == b.charAt(i)) i++;
if (i == len) {
if (a.length() > b.length()) return false; // a longer word may not precede its own prefix
} else if (rank[a.charAt(i) - 'a'] > rank[b.charAt(i) - 'a']) {
return false;
}
}
return true;
}
Complexity. Time O(k! × N × L) where L is the word length: k! candidate alphabets, each checked against all adjacent pairs. For k = 26 that is about 4 × 10^26 candidates, so this only runs for tiny k. Space O(k) for the recursion and the rank table.
Approach 2: Optimal, build a graph and topologically sort it
Intuition. Each adjacent pair gives at most one fact of the form “x before y”. Make each letter a node and each fact an edge x → y. An alphabet consistent with the list is exactly an ordering of the letters in which every edge points forward: a topological order. Kahn’s algorithm finds one, or tells us none exists because the facts contain a cycle (a before b and b before a).
Letters that never appear in any fact have in-degree 0 and are emitted along with the rest; they may go anywhere, and Kahn’s simply places them early.
Algorithm.
- For each adjacent pair, find the first differing position
i.- No difference and the upper word is longer: return
"". - No difference otherwise: skip, the pair gives no information.
- Else add the edge
upper[i] → lower[i]and increase the in-degree oflower[i].
- No difference and the upper word is longer: return
- Queue every letter with in-degree
0. - Pop a letter, append it, decrement its successors, queue any that reach
0. - If all
kletters were appended, return the string; otherwise a cycle exists, so return"".
static String optimal(String[] words, int k) {
List<List<Integer>> adj = new ArrayList<>();
for (int i = 0; i < k; i++) adj.add(new ArrayList<>());
int[] indegree = new int[k];
for (int w = 0; w + 1 < words.length; w++) {
String a = words[w], b = words[w + 1];
int len = Math.min(a.length(), b.length()), i = 0;
while (i < len && a.charAt(i) == b.charAt(i)) i++;
if (i == len) {
if (a.length() > b.length()) return ""; // "abc" before "ab": no order can explain it
continue; // a is a prefix of b: no information
}
int u = a.charAt(i) - 'a', v = b.charAt(i) - 'a';
adj.get(u).add(v); // letter u comes before letter v
indegree[v]++;
}
Deque<Integer> queue = new ArrayDeque<>();
for (int c = 0; c < k; c++) if (indegree[c] == 0) queue.add(c);
StringBuilder order = new StringBuilder();
while (!queue.isEmpty()) {
int u = queue.poll();
order.append((char) ('a' + u));
for (int v : adj.get(u)) {
if (--indegree[v] == 0) queue.add(v);
}
}
return order.length() == k ? order.toString() : ""; // letters left over sit on a cycle
}
Dry run on Example 1. The adjacent pairs give:
| pair | first difference | edge |
|---|---|---|
| baa / abcd | b vs a | b → a |
| abcd / abca | d vs a | d → a |
| abca / cab | a vs c | a → c |
| cab / cad | b vs d | b → d |
In-degrees are a: 2, b: 0, c: 1, d: 1. Kahn’s algorithm then runs:
| pop | output | in-degree changes | queue after |
|---|---|---|---|
| start | [b] | ||
| b | b | a: 2 → 1, d: 1 → 0 | [d] |
| d | bd | a: 1 → 0 | [a] |
| a | bda | c: 1 → 0 | [c] |
| c | bdac | none | [] |
All four letters were output, so the answer is "bdac". Here the order is forced; in general several answers may be valid.
Complexity. Time O(N × L + k): comparing adjacent pairs reads each character at most twice, there are at most N - 1 edges, and Kahn’s algorithm is linear in letters plus edges. Space O(k + N) for the adjacency list, the in-degree array and the queue.
Edge cases and pitfalls
- Prefix rule.
["abc", "ab"]is impossible and must return"". Forgetting this check silently returns some alphabet, because the pair adds no edge. - Equal words and true prefixes.
["ab", "ab", "abc"]produces no edges at all; any order of the letters is fine. - Only the first difference counts. Adding edges for later positions invents constraints. From
"baa"above"abcd", position 1 would suggesta → b, which contradicts the real factb → aand creates a false cycle. - Comparing non-adjacent words. It is not wrong, just unnecessary, and it turns the
O(N)pairs intoO(N^2). - Duplicate edges. Two pairs may both say
x → y. The in-degree counts both, and both are decremented, so no de-duplication is needed. - Contradictions.
["a", "b", "a"]givesa → bandb → a. Neither letter reaches in-degree0, fewer thankletters are output, and the answer is"". - Unconstrained letters. With
k = 3and["b", "a"],cappears in no fact. It still has to be in the answer, because the output must contain allkletters.
Related problems
- Topological sort or Kahn’s algorithm is the algorithm used once the graph is built.
- Topo Sort gives the DFS alternative for producing the order.
- Course Schedule II returns a topological order of courses, with the same “empty if a cycle” rule.
- Course Schedule I asks only whether the order exists.
- Detect a cycle in a directed graph explains why leftover nodes in Kahn’s algorithm mean a cycle.