150NeetCode 150

97.Alien Dictionary

HardBlind 75Graphs ยท 18/19

Problem

An alien language uses the lowercase English letters, but in an unknown order. You are given a list of words from the alien dictionary, already sorted lexicographically by the alien rules.

Return a string containing every unique letter that appears in words, ordered according to the alien alphabet. If the ordering is contradictory (no valid order exists), return "". If several orders are valid, any of them is accepted.

Examples

Input:  words = ["wrt","wrf","er","ett","rftt"]
Output: "wertf"
Explanation: wrt<wrf gives t<f, wrf<er gives w<e, er<ett gives r<t, ett<rftt gives e<r.
             Chain: w < e < r < t < f.
Input:  words = ["z","x","z"]
Output: ""
Explanation: z<x and x<z contradict each other (a cycle).
Input:  words = ["abc","ab"]
Output: ""
Explanation: A longer word can never come before its own prefix, so the input is invalid.

Constraints

  • 1 <= words.length <= 100
  • 1 <= words[i].length <= 100
  • words[i] consists of lowercase English letters.

๐Ÿ’ก Key Insight

Comparing adjacent words gives all the information we can extract: the first position where they differ tells us c1 comes before c2; nothing after that position matters. These facts form a directed graph over letters, and a valid alphabet is exactly a topological order of that graph. A cycle means a contradiction (return ""), and so does a word followed by its own proper prefix.

DFS Topological Sort

TimeO(C)SpaceO(U + E)

Intuition

Build the letter graph, then run DFS with three states per letter: unvisited, visiting (on the current path), and done. Reaching a "visiting" letter means a cycle. When a letter finishes, append it โ€” post-order gives the reverse topological order, so reverse at the end.

Algorithm

  1. Add every letter in words as a node.
  2. For each adjacent pair (w1, w2): find the first differing index j; add edge w1[j] -> w2[j]. If no difference and w1.length() > w2.length(), return "".
  3. DFS from every unvisited letter; return "" on a cycle; append each letter after its neighbours finish.
  4. Reverse the built string.

Visualization

graph: t->f, w->e, r->t, e->r

dfs(w) -> dfs(e) -> dfs(r) -> dfs(t) -> dfs(f)
post-order: f, t, r, e, w
reversed:   w, e, r, t, f  -> "wertf"

Code

java
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

class Solution {
    private final Map<Character, Set<Character>> adj = new HashMap<>();
    private final Map<Character, Integer> state = new HashMap<>(); // 1 = visiting, 2 = done
    private final StringBuilder order = new StringBuilder();

    public String alienOrder(String[] words) {
        for (String w : words) {
            for (char c : w.toCharArray()) {
                adj.putIfAbsent(c, new HashSet<>());
            }
        }
        for (int i = 0; i + 1 < words.length; i++) {
            String w1 = words[i], w2 = words[i + 1];
            int len = Math.min(w1.length(), w2.length());
            if (w1.length() > w2.length() && w1.startsWith(w2)) {
                return ""; // longer word before its own prefix
            }
            for (int j = 0; j < len; j++) {
                if (w1.charAt(j) != w2.charAt(j)) {
                    adj.get(w1.charAt(j)).add(w2.charAt(j));
                    break; // only the first difference matters
                }
            }
        }
        for (char c : adj.keySet()) {
            if (!dfs(c)) {
                return "";
            }
        }
        return order.reverse().toString();
    }

    // returns false if a cycle is found
    private boolean dfs(char c) {
        Integer s = state.get(c);
        if (s != null) {
            return s == 2; // 1 means we are still on the path: cycle
        }
        state.put(c, 1);
        for (char next : adj.get(c)) {
            if (!dfs(next)) {
                return false;
            }
        }
        state.put(c, 2);
        order.append(c); // post-order
        return true;
    }
}

Complexity

  • Time: O(C) โ€” C is the total number of characters across all words (building edges), plus O(U + E) for the DFS where U <= 26.
  • Space: O(U + E) โ€” at most 26 nodes and 26ยฒ edges.

Complexity Comparison

Approach Time Space
DFS Topological Sort O(C) O(U + E)
Kahn's Algorithm O(C) O(U + E)

Common Pitfalls

  • Comparing every character of two adjacent words โ€” only the first difference creates an edge.
  • Missing the prefix case ["abc","ab"], which must return "".
  • Forgetting letters that never appear in any edge (e.g. a single word ["z"] should return "z").
  • Incrementing in-degree twice for a duplicate edge, which leaves a letter stuck with in-degree 1.
  • Comparing non-adjacent words โ€” unnecessary, since order is transitive.