Bird
Raised Fist0
Interview Prepchallenge-problemshardFacebookAmazonGoogleBloomberg

Alien Dictionary

Choose your preparation mode4 modes available
🎯
Alien Dictionary
hardMIXEDFacebookAmazonGoogle

Imagine discovering a new alien language with words sorted in an unknown order. You want to deduce the alphabet order from a sorted dictionary of alien words.

💡 This problem involves inferring order relations from partial information, which is tricky because the order isn't explicitly given. Beginners often struggle because it requires building and reasoning about a graph and performing a topological sort, which is a non-trivial concept.
📋
Problem Statement

Given a list of words sorted lexicographically in an alien language, derive the order of characters in the alien alphabet. Return a string representing the characters in the correct order. If no valid order exists, return an empty string.

1 ≤ number of words ≤ 10^51 ≤ length of each word ≤ 100All words contain only lowercase English letters
💡
Example
Input["wrt","wrf","er","ett","rftt"]
Outputwertf

From 'wrt' and 'wrf', we know 't' < 'f'. From 'wrt' and 'er', 'w' < 'e'. From 'er' and 'ett', 'r' < 't'. From 'ett' and 'rftt', 'e' < 'r'. Combining these gives the order 'w', 'e', 'r', 't', 'f'.

  • Words with single character → order is that character
  • Prefix case: ['abc', 'ab'] → invalid order, return empty string
  • All words identical → order is all unique chars in any order
  • Words with no ordering info between some chars → multiple valid orders possible
⚠️
Common Mistakes
Not checking prefix invalid case where a longer word precedes its prefix

Returns incorrect order or fails to detect invalid input

✅ Add explicit check comparing prefixes and return empty string if invalid

Adding duplicate edges and incrementing in-degree multiple times

In-degree counts become incorrect, causing wrong topological order or false cycle detection

✅ Use sets to store edges and check before adding to avoid duplicates

Using DFS without proper cycle detection states leading to infinite recursion

Stack overflow or incorrect cycle detection

✅ Use three-state visitation (unvisited, visiting, visited) to detect cycles correctly

Not including all unique characters in the graph and output

Missing characters in final order

✅ Initialize graph and in-degree with all unique characters from input

Returning output without reversing DFS postorder

Incorrect character order

✅ Reverse the output list after DFS to get correct topological order

🧠
Brute Force (Pairwise Comparison and Cycle Detection via DFS)
💡 This approach exists to build intuition by explicitly comparing every adjacent word pair to infer character order and then checking for cycles by DFS. It is straightforward but inefficient for large inputs.

Intuition

Compare each pair of adjacent words to find the first differing character, which gives a direct ordering between two characters. Build a graph of these relations and detect cycles using DFS to confirm if a valid order exists.

Algorithm

  1. Initialize a graph with all unique characters as nodes.
  2. Compare each adjacent pair of words to find the first differing character and add a directed edge from the first to the second.
  3. Perform DFS on the graph to detect cycles and to generate a topological order.
  4. If a cycle is detected, return an empty string; otherwise, return the topological order.
💡 The challenge is to carefully build the graph and then use DFS to both detect cycles and produce the order, which can be confusing at first.
</>
Code
from collections import defaultdict
import sys
sys.setrecursionlimit(10**7)

def alienOrder(words):
    graph = defaultdict(set)
    all_chars = set(''.join(words))

    # Build graph edges
    for i in range(len(words) - 1):
        w1, w2 = words[i], words[i+1]
        min_len = min(len(w1), len(w2))
        # Check prefix invalid case
        if len(w1) > len(w2) and w1[:min_len] == w2[:min_len]:
            return ""
        for j in range(min_len):
            if w1[j] != w2[j]:
                graph[w1[j]].add(w2[j])
                break

    visited = {}  # char: 0=unvisited, 1=visiting, 2=visited
    output = []

    def dfs(c):
        if c in visited:
            return visited[c] == 2
        visited[c] = 1
        for nei in graph[c]:
            if not dfs(nei):
                return False
        visited[c] = 2
        output.append(c)
        return True

    for c in all_chars:
        if c not in visited:
            if not dfs(c):
                return ""

    return ''.join(output[::-1])

# Driver code
if __name__ == '__main__':
    words = ["wrt", "wrf", "er", "ett", "rftt"]
    print(alienOrder(words))
Line Notes
graph = defaultdict(set)Initialize adjacency list to store edges from one char to another
all_chars = set(''.join(words))Collect all unique characters to ensure all nodes are included
if len(w1) > len(w2) and w1[:min_len] == w2[:min_len]Detect invalid prefix case where longer word comes before its prefix
graph[w1[j]].add(w2[j])Add directed edge indicating w1[j] comes before w2[j]
visited = {}Track visitation state to detect cycles during DFS
def dfs(c):Recursive DFS function to detect cycles and build topological order
if c in visited: return visited[c] == 2If node is fully processed, no cycle here; if visiting, cycle detected
visited[c] = 1Mark node as visiting (in recursion stack)
for nei in graph[c]:Visit all neighbors to check for cycles
visited[c] = 2Mark node as fully visited (processed)
output.append(c)Add node to output after all descendants processed
for c in all_chars: if c not in visited:Start DFS from all nodes to cover disconnected graph
return ''.join(output[::-1])Reverse output to get correct topological order
import java.util.*;

public class AlienDictionary {
    public String alienOrder(String[] words) {
        Map<Character, Set<Character>> graph = new HashMap<>();
        Set<Character> allChars = new HashSet<>();
        for (String w : words) {
            for (char c : w.toCharArray()) allChars.add(c);
        }

        for (int i = 0; i < words.length - 1; i++) {
            String w1 = words[i], w2 = words[i + 1];
            int minLen = Math.min(w1.length(), w2.length());
            if (w1.length() > w2.length() && w1.startsWith(w2)) return "";
            for (int j = 0; j < minLen; j++) {
                if (w1.charAt(j) != w2.charAt(j)) {
                    graph.computeIfAbsent(w1.charAt(j), k -> new HashSet<>()).add(w2.charAt(j));
                    break;
                }
            }
        }

        Map<Character, Integer> visited = new HashMap<>(); // 0=unvisited,1=visiting,2=visited
        StringBuilder output = new StringBuilder();

        for (char c : allChars) {
            if (!visited.containsKey(c)) {
                if (!dfs(c, graph, visited, output)) return "";
            }
        }

        return output.reverse().toString();
    }

    private boolean dfs(char c, Map<Character, Set<Character>> graph, Map<Character, Integer> visited, StringBuilder output) {
        if (visited.containsKey(c)) {
            return visited.get(c) == 2;
        }
        visited.put(c, 1);
        for (char nei : graph.getOrDefault(c, Collections.emptySet())) {
            if (!dfs(nei, graph, visited, output)) return false;
        }
        visited.put(c, 2);
        output.append(c);
        return true;
    }

    // Driver
    public static void main(String[] args) {
        AlienDictionary sol = new AlienDictionary();
        String[] words = {"wrt", "wrf", "er", "ett", "rftt"};
        System.out.println(sol.alienOrder(words));
    }
}
Line Notes
Map<Character, Set<Character>> graph = new HashMap<>()Adjacency list to store edges
Set<Character> allChars = new HashSet<>()Collect all unique characters
if (w1.length() > w2.length() && w1.startsWith(w2)) return ""Detect invalid prefix ordering
graph.computeIfAbsent(w1.charAt(j), k -> new HashSet<>()).add(w2.charAt(j))Add directed edge from w1[j] to w2[j]
Map<Character, Integer> visited = new HashMap<>()Track visitation state for cycle detection
if (visited.containsKey(c)) { return visited.get(c) == 2; }If node fully processed, no cycle; if visiting, cycle detected
visited.put(c, 1)Mark node as visiting
for (char nei : graph.getOrDefault(c, Collections.emptySet()))Visit all neighbors
visited.put(c, 2)Mark node as fully visited
output.append(c)Add node to output after processing neighbors
return output.reverse().toString()Reverse output to get correct order
for (char c : allChars)Start DFS from all nodes to cover disconnected graph
#include <iostream>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <string>
#include <algorithm>

using namespace std;

class Solution {
public:
    string alienOrder(vector<string>& words) {
        unordered_map<char, unordered_set<char>> graph;
        unordered_set<char> allChars;
        for (auto& w : words) {
            for (char c : w) allChars.insert(c);
        }

        for (int i = 0; i < (int)words.size() - 1; i++) {
            string& w1 = words[i], &w2 = words[i+1];
            int minLen = min(w1.size(), w2.size());
            if (w1.size() > w2.size() && w1.substr(0, minLen) == w2.substr(0, minLen))
                return "";
            for (int j = 0; j < minLen; j++) {
                if (w1[j] != w2[j]) {
                    graph[w1[j]].insert(w2[j]);
                    break;
                }
            }
        }

        unordered_map<char, int> visited; // 0=unvisited,1=visiting,2=visited
        string output;

        function<bool(char)> dfs = [&](char c) {
            if (visited.count(c)) return visited[c] == 2;
            visited[c] = 1;
            for (char nei : graph[c]) {
                if (!dfs(nei)) return false;
            }
            visited[c] = 2;
            output.push_back(c);
            return true;
        };

        for (char c : allChars) {
            if (!visited.count(c)) {
                if (!dfs(c)) return "";
            }
        }

        reverse(output.begin(), output.end());
        return output;
    }
};

// Driver
int main() {
    Solution sol;
    vector<string> words = {"wrt", "wrf", "er", "ett", "rftt"};
    cout << sol.alienOrder(words) << endl;
    return 0;
}
Line Notes
unordered_map<char, unordered_set<char>> graphAdjacency list for graph edges
unordered_set<char> allCharsCollect all unique characters
if (w1.size() > w2.size() && w1.substr(0, minLen) == w2.substr(0, minLen)) return ""Detect invalid prefix ordering
graph[w1[j]].insert(w2[j])Add directed edge from w1[j] to w2[j]
unordered_map<char, int> visitedTrack visitation state for cycle detection
if (visited.count(c)) return visited[c] == 2If node fully processed, no cycle; if visiting, cycle detected
visited[c] = 1Mark node as visiting
for (char nei : graph[c])Visit all neighbors
visited[c] = 2Mark node as fully visited
output.push_back(c)Add node to output after processing neighbors
reverse(output.begin(), output.end())Reverse output to get correct order
for (char c : allChars)Start DFS from all nodes to cover disconnected graph
var alienOrder = function(words) {
    const graph = new Map();
    const allChars = new Set(words.join(''));

    for (let i = 0; i < words.length - 1; i++) {
        const w1 = words[i], w2 = words[i+1];
        const minLen = Math.min(w1.length, w2.length);
        if (w1.length > w2.length && w1.slice(0, minLen) === w2.slice(0, minLen)) return "";
        for (let j = 0; j < minLen; j++) {
            if (w1[j] !== w2[j]) {
                if (!graph.has(w1[j])) graph.set(w1[j], new Set());
                graph.get(w1[j]).add(w2[j]);
                break;
            }
        }
    }

    const visited = new Map(); // 0=unvisited,1=visiting,2=visited
    const output = [];

    function dfs(c) {
        if (visited.has(c)) return visited.get(c) === 2;
        visited.set(c, 1);
        if (graph.has(c)) {
            for (const nei of graph.get(c)) {
                if (!dfs(nei)) return false;
            }
        }
        visited.set(c, 2);
        output.push(c);
        return true;
    }

    for (const c of allChars) {
        if (!visited.has(c)) {
            if (!dfs(c)) return "";
        }
    }

    return output.reverse().join('');
};

// Driver
const words = ["wrt", "wrf", "er", "ett", "rftt"];
console.log(alienOrder(words));
Line Notes
const graph = new Map()Adjacency list to store edges
const allChars = new Set(words.join(''))Collect all unique characters
if (w1.length > w2.length && w1.slice(0, minLen) === w2.slice(0, minLen)) return ""Detect invalid prefix ordering
graph.get(w1[j]).add(w2[j])Add directed edge from w1[j] to w2[j]
const visited = new Map()Track visitation state for cycle detection
if (visited.has(c)) return visited.get(c) === 2If node fully visited, no cycle here; if visiting, cycle detected
visited.set(c, 1)Mark node as visiting
if (graph.has(c)) { for (const nei of graph.get(c)) }Visit all neighbors
visited.set(c, 2)Mark node as fully visited
output.push(c)Add node to output after processing neighbors
return output.reverse().join('')Reverse output to get correct order
for (const c of allChars)Start DFS from all nodes to cover disconnected graph
⏱
Complexity
TimeO(C) where C is total characters in all words plus edges for graph construction and DFS
SpaceO(U + E) where U is number of unique chars and E is number of edges

We compare adjacent words once, building edges for differing chars. DFS visits each node and edge once.

💡 For 100,000 words with average length 10, this means roughly 1 million operations, which is borderline for brute force but still feasible with efficient implementation.
Interview Verdict: Accepted but not optimal for very large inputs

This approach is good to understand the problem and graph building but can be slow for huge inputs due to repeated DFS calls.

🧠
Better Approach (Kahn's Algorithm for Topological Sort)
💡 This approach improves efficiency by using BFS (Kahn's algorithm) to perform topological sort, avoiding recursion and making cycle detection straightforward.

Intuition

Build the graph and compute in-degree for each node. Start with nodes having zero in-degree and iteratively remove them, updating neighbors' in-degree. If all nodes are processed, the order is valid.

Algorithm

  1. Build graph and compute in-degree for each character.
  2. Initialize a queue with all characters having zero in-degree.
  3. While the queue is not empty, remove a character, append it to the output, and reduce in-degree of its neighbors.
  4. If all characters are processed, return the output; otherwise, return empty string due to cycle.
💡 This approach is easier to reason about for cycle detection and often preferred in interviews for topological sorting.
</>
Code
from collections import defaultdict, deque

def alienOrder(words):
    graph = defaultdict(set)
    in_degree = {c: 0 for word in words for c in word}

    for i in range(len(words) - 1):
        w1, w2 = words[i], words[i+1]
        min_len = min(len(w1), len(w2))
        if len(w1) > len(w2) and w1[:min_len] == w2[:min_len]:
            return ""
        for j in range(min_len):
            if w1[j] != w2[j]:
                if w2[j] not in graph[w1[j]]:
                    graph[w1[j]].add(w2[j])
                    in_degree[w2[j]] += 1
                break

    queue = deque([c for c in in_degree if in_degree[c] == 0])
    output = []

    while queue:
        c = queue.popleft()
        output.append(c)
        for nei in graph[c]:
            in_degree[nei] -= 1
            if in_degree[nei] == 0:
                queue.append(nei)

    if len(output) != len(in_degree):
        return ""
    return ''.join(output)

# Driver code
if __name__ == '__main__':
    words = ["wrt", "wrf", "er", "ett", "rftt"]
    print(alienOrder(words))
Line Notes
in_degree = {c: 0 for word in words for c in word}Initialize in-degree for all unique chars to zero
if len(w1) > len(w2) and w1[:min_len] == w2[:min_len]Detect invalid prefix ordering
if w2[j] not in graph[w1[j]]Avoid duplicate edges to prevent incorrect in-degree increments
in_degree[w2[j]] += 1Increment in-degree for neighbor
queue = deque([c for c in in_degree if in_degree[c] == 0])Start BFS with nodes having zero in-degree
while queue: c = queue.popleft()Process nodes in BFS order
in_degree[nei] -= 1Decrease in-degree of neighbors after removing current node
if in_degree[nei] == 0: queue.append(nei)Add neighbors with zero in-degree to queue
if len(output) != len(in_degree)Check if all nodes processed to detect cycle
return ''.join(output)Return the topological order
import java.util.*;

public class AlienDictionary {
    public String alienOrder(String[] words) {
        Map<Character, Set<Character>> graph = new HashMap<>();
        Map<Character, Integer> inDegree = new HashMap<>();
        for (String w : words) {
            for (char c : w.toCharArray()) inDegree.put(c, 0);
        }

        for (int i = 0; i < words.length - 1; i++) {
            String w1 = words[i], w2 = words[i + 1];
            int minLen = Math.min(w1.length(), w2.length());
            if (w1.length() > w2.length() && w1.startsWith(w2)) return "";
            for (int j = 0; j < minLen; j++) {
                if (w1.charAt(j) != w2.charAt(j)) {
                    graph.computeIfAbsent(w1.charAt(j), k -> new HashSet<>());
                    if (!graph.get(w1.charAt(j)).contains(w2.charAt(j))) {
                        graph.get(w1.charAt(j)).add(w2.charAt(j));
                        inDegree.put(w2.charAt(j), inDegree.get(w2.charAt(j)) + 1);
                    }
                    break;
                }
            }
        }

        Queue<Character> queue = new LinkedList<>();
        for (char c : inDegree.keySet()) {
            if (inDegree.get(c) == 0) queue.offer(c);
        }

        StringBuilder output = new StringBuilder();
        while (!queue.isEmpty()) {
            char c = queue.poll();
            output.append(c);
            if (graph.containsKey(c)) {
                for (char nei : graph.get(c)) {
                    inDegree.put(nei, inDegree.get(nei) - 1);
                    if (inDegree.get(nei) == 0) queue.offer(nei);
                }
            }
        }

        if (output.length() != inDegree.size()) return "";
        return output.toString();
    }

    // Driver
    public static void main(String[] args) {
        AlienDictionary sol = new AlienDictionary();
        String[] words = {"wrt", "wrf", "er", "ett", "rftt"};
        System.out.println(sol.alienOrder(words));
    }
}
Line Notes
Map<Character, Integer> inDegree = new HashMap<>()Initialize in-degree map for all chars
if (w1.length() > w2.length() && w1.startsWith(w2)) return ""Detect invalid prefix ordering
if (!graph.get(w1.charAt(j)).contains(w2.charAt(j)))Avoid duplicate edges
inDegree.put(w2.charAt(j), inDegree.get(w2.charAt(j)) + 1)Increment in-degree for neighbor
Queue<Character> queue = new LinkedList<>()Queue for BFS processing
if (inDegree.get(c) == 0) queue.offer(c)Add zero in-degree nodes to queue
while (!queue.isEmpty())Process nodes in BFS order
inDegree.put(nei, inDegree.get(nei) - 1)Decrease in-degree after removing node
if (inDegree.get(nei) == 0) queue.offer(nei)Add neighbors with zero in-degree
if (output.length() != inDegree.size()) return ""Detect cycle if not all nodes processed
#include <iostream>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <queue>
#include <string>

using namespace std;

class Solution {
public:
    string alienOrder(vector<string>& words) {
        unordered_map<char, unordered_set<char>> graph;
        unordered_map<char, int> inDegree;
        for (auto& w : words) {
            for (char c : w) inDegree[c] = 0;
        }

        for (int i = 0; i < (int)words.size() - 1; i++) {
            string& w1 = words[i], &w2 = words[i+1];
            int minLen = min(w1.size(), w2.size());
            if (w1.size() > w2.size() && w1.substr(0, minLen) == w2.substr(0, minLen))
                return "";
            for (int j = 0; j < minLen; j++) {
                if (w1[j] != w2[j]) {
                    if (graph[w1[j]].count(w2[j]) == 0) {
                        graph[w1[j]].insert(w2[j]);
                        inDegree[w2[j]]++;
                    }
                    break;
                }
            }
        }

        queue<char> q;
        for (auto& [c, deg] : inDegree) {
            if (deg == 0) q.push(c);
        }

        string output;
        while (!q.empty()) {
            char c = q.front(); q.pop();
            output.push_back(c);
            for (char nei : graph[c]) {
                inDegree[nei]--;
                if (inDegree[nei] == 0) q.push(nei);
            }
        }

        if ((int)output.size() != (int)inDegree.size()) return "";
        return output;
    }
};

// Driver
int main() {
    Solution sol;
    vector<string> words = {"wrt", "wrf", "er", "ett", "rftt"};
    cout << sol.alienOrder(words) << endl;
    return 0;
}
Line Notes
unordered_map<char, int> inDegreeTrack in-degree for each character
if (w1.size() > w2.size() && w1.substr(0, minLen) == w2.substr(0, minLen)) return ""Detect invalid prefix ordering
if (graph[w1[j]].count(w2[j]) == 0)Avoid duplicate edges
inDegree[w2[j]]++Increment in-degree for neighbor
queue<char> qQueue for BFS processing
if (deg == 0) q.push(c)Add zero in-degree nodes to queue
while (!q.empty())Process nodes in BFS order
inDegree[nei]--Decrease in-degree after removing node
if (inDegree[nei] == 0) q.push(nei)Add neighbors with zero in-degree
if ((int)output.size() != (int)inDegree.size()) return ""Detect cycle if not all nodes processed
var alienOrder = function(words) {
    const graph = new Map();
    const inDegree = new Map();
    for (const w of words) {
        for (const c of w) inDegree.set(c, 0);
    }

    for (let i = 0; i < words.length - 1; i++) {
        const w1 = words[i], w2 = words[i+1];
        const minLen = Math.min(w1.length, w2.length);
        if (w1.length > w2.length && w1.slice(0, minLen) === w2.slice(0, minLen)) return "";
        for (let j = 0; j < minLen; j++) {
            if (w1[j] !== w2[j]) {
                if (!graph.has(w1[j])) graph.set(w1[j], new Set());
                if (!graph.get(w1[j]).has(w2[j])) {
                    graph.get(w1[j]).add(w2[j]);
                    inDegree.set(w2[j], inDegree.get(w2[j]) + 1);
                }
                break;
            }
        }
    }

    const queue = [];
    for (const [c, deg] of inDegree.entries()) {
        if (deg === 0) queue.push(c);
    }

    const output = [];
    while (queue.length > 0) {
        const c = queue.shift();
        output.push(c);
        if (graph.has(c)) {
            for (const nei of graph.get(c)) {
                inDegree.set(nei, inDegree.get(nei) - 1);
                if (inDegree.get(nei) === 0) queue.push(nei);
            }
        }
    }

    if (output.length !== inDegree.size) return "";
    return output.join('');
};

// Driver
const words = ["wrt", "wrf", "er", "ett", "rftt"];
console.log(alienOrder(words));
Line Notes
const inDegree = new Map()Initialize in-degree map for all chars
if (w1.length > w2.length && w1.slice(0, minLen) === w2.slice(0, minLen)) return ""Detect invalid prefix ordering
if (!graph.get(w1[j]).has(w2[j]))Avoid duplicate edges
inDegree.set(w2[j], inDegree.get(w2[j]) + 1)Increment in-degree for neighbor
const queue = []Queue for BFS processing
if (deg === 0) queue.push(c)Add zero in-degree nodes to queue
while (queue.length > 0)Process nodes in BFS order
inDegree.set(nei, inDegree.get(nei) - 1)Decrease in-degree after removing node
if (inDegree.get(nei) === 0) queue.push(nei)Add neighbors with zero in-degree
if (output.length !== inDegree.size) return ""Detect cycle if not all nodes processed
⏱
Complexity
TimeO(C) where C is total characters plus edges, BFS visits each node and edge once
SpaceO(U + E) for graph and in-degree storage

Building graph and in-degree takes O(C), BFS processes each node and edge once, making it efficient for large inputs.

💡 For 100,000 words with average length 10, this approach handles about 1 million operations efficiently using BFS.
Interview Verdict: Accepted and preferred approach in interviews

This approach is optimal for this problem and easier to implement correctly under interview pressure.

🧠
Optimal Approach (Kahn's Algorithm with Early Cycle Detection and Edge Deduplication)
💡 This approach refines the BFS method by carefully handling duplicate edges and early cycle detection to improve runtime and correctness on edge cases.

Intuition

By ensuring no duplicate edges and checking prefix conditions early, we avoid unnecessary processing and detect invalid inputs sooner, making the solution robust and fast.

Algorithm

  1. Initialize graph and in-degree map with all unique characters.
  2. For each adjacent word pair, check prefix invalid case and add edges only if not already present.
  3. Use a queue to perform BFS topological sort, processing nodes with zero in-degree.
  4. If all nodes are processed, return the order; else return empty string indicating cycle.
💡 This approach is a polished version of BFS topological sort that handles tricky edge cases and optimizes graph construction.
</>
Code
from collections import defaultdict, deque

def alienOrder(words):
    graph = defaultdict(set)
    in_degree = {c: 0 for word in words for c in word}

    for i in range(len(words) - 1):
        w1, w2 = words[i], words[i+1]
        min_len = min(len(w1), len(w2))
        if len(w1) > len(w2) and w1[:min_len] == w2[:min_len]:
            return ""
        for j in range(min_len):
            if w1[j] != w2[j]:
                if w2[j] not in graph[w1[j]]:
                    graph[w1[j]].add(w2[j])
                    in_degree[w2[j]] += 1
                break

    queue = deque([c for c in in_degree if in_degree[c] == 0])
    output = []

    while queue:
        c = queue.popleft()
        output.append(c)
        for nei in graph[c]:
            in_degree[nei] -= 1
            if in_degree[nei] == 0:
                queue.append(nei)

    if len(output) != len(in_degree):
        return ""
    return ''.join(output)

# Driver code
if __name__ == '__main__':
    words = ["wrt", "wrf", "er", "ett", "rftt"]
    print(alienOrder(words))
Line Notes
in_degree = {c: 0 for word in words for c in word}Initialize in-degree for all unique chars
if len(w1) > len(w2) and w1[:min_len] == w2[:min_len]Early detection of invalid prefix ordering
if w2[j] not in graph[w1[j]]Avoid duplicate edges to prevent incorrect in-degree increments
in_degree[w2[j]] += 1Increment in-degree for neighbor
queue = deque([c for c in in_degree if in_degree[c] == 0])Initialize queue with zero in-degree nodes
while queue: c = queue.popleft()Process nodes in BFS order
in_degree[nei] -= 1Decrease in-degree after removing node
if in_degree[nei] == 0: queue.append(nei)Add neighbors with zero in-degree
if len(output) != len(in_degree)Check for cycle by comparing processed nodes count
return ''.join(output)Return the topological order
import java.util.*;

public class AlienDictionary {
    public String alienOrder(String[] words) {
        Map<Character, Set<Character>> graph = new HashMap<>();
        Map<Character, Integer> inDegree = new HashMap<>();
        for (String w : words) {
            for (char c : w.toCharArray()) inDegree.put(c, 0);
        }

        for (int i = 0; i < words.length - 1; i++) {
            String w1 = words[i], w2 = words[i + 1];
            int minLen = Math.min(w1.length(), w2.length());
            if (w1.length() > w2.length() && w1.startsWith(w2)) return "";
            for (int j = 0; j < minLen; j++) {
                if (w1.charAt(j) != w2.charAt(j)) {
                    graph.computeIfAbsent(w1.charAt(j), k -> new HashSet<>());
                    if (!graph.get(w1.charAt(j)).contains(w2.charAt(j))) {
                        graph.get(w1.charAt(j)).add(w2.charAt(j));
                        inDegree.put(w2.charAt(j), inDegree.get(w2.charAt(j)) + 1);
                    }
                    break;
                }
            }
        }

        Queue<Character> queue = new LinkedList<>();
        for (char c : inDegree.keySet()) {
            if (inDegree.get(c) == 0) queue.offer(c);
        }

        StringBuilder output = new StringBuilder();
        while (!queue.isEmpty()) {
            char c = queue.poll();
            output.append(c);
            if (graph.containsKey(c)) {
                for (char nei : graph.get(c)) {
                    inDegree.put(nei, inDegree.get(nei) - 1);
                    if (inDegree.get(nei) == 0) queue.offer(nei);
                }
            }
        }

        if (output.length() != inDegree.size()) return "";
        return output.toString();
    }

    // Driver
    public static void main(String[] args) {
        AlienDictionary sol = new AlienDictionary();
        String[] words = {"wrt", "wrf", "er", "ett", "rftt"};
        System.out.println(sol.alienOrder(words));
    }
}
Line Notes
Map<Character, Integer> inDegree = new HashMap<>()Initialize in-degree map
if (w1.length() > w2.length() && w1.startsWith(w2)) return ""Early invalid prefix detection
if (!graph.get(w1.charAt(j)).contains(w2.charAt(j)))Avoid duplicate edges
inDegree.put(w2.charAt(j), inDegree.get(w2.charAt(j)) + 1)Increment in-degree
Queue<Character> queue = new LinkedList<>()Queue for BFS
if (inDegree.get(c) == 0) queue.offer(c)Add zero in-degree nodes
while (!queue.isEmpty())Process BFS
inDegree.put(nei, inDegree.get(nei) - 1)Decrease in-degree
if (inDegree.get(nei) == 0) queue.offer(nei)Add neighbors with zero in-degree
if (output.length() != inDegree.size()) return ""Cycle detection
#include <iostream>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <queue>
#include <string>

using namespace std;

class Solution {
public:
    string alienOrder(vector<string>& words) {
        unordered_map<char, unordered_set<char>> graph;
        unordered_map<char, int> inDegree;
        for (auto& w : words) {
            for (char c : w) inDegree[c] = 0;
        }

        for (int i = 0; i < (int)words.size() - 1; i++) {
            string& w1 = words[i], &w2 = words[i+1];
            int minLen = min(w1.size(), w2.size());
            if (w1.size() > w2.size() && w1.substr(0, minLen) == w2.substr(0, minLen))
                return "";
            for (int j = 0; j < minLen; j++) {
                if (w1[j] != w2[j]) {
                    if (graph[w1[j]].count(w2[j]) == 0) {
                        graph[w1[j]].insert(w2[j]);
                        inDegree[w2[j]]++;
                    }
                    break;
                }
            }
        }

        queue<char> q;
        for (auto& [c, deg] : inDegree) {
            if (deg == 0) q.push(c);
        }

        string output;
        while (!q.empty()) {
            char c = q.front(); q.pop();
            output.push_back(c);
            for (char nei : graph[c]) {
                inDegree[nei]--;
                if (inDegree[nei] == 0) q.push(nei);
            }
        }

        if ((int)output.size() != (int)inDegree.size()) return "";
        return output;
    }
};

// Driver
int main() {
    Solution sol;
    vector<string> words = {"wrt", "wrf", "er", "ett", "rftt"};
    cout << sol.alienOrder(words) << endl;
    return 0;
}
Line Notes
unordered_map<char, int> inDegreeTrack in-degree for each character
if (w1.size() > w2.size() && w1.substr(0, minLen) == w2.substr(0, minLen)) return ""Early invalid prefix detection
if (graph[w1[j]].count(w2[j]) == 0)Avoid duplicate edges
inDegree[w2[j]]++Increment in-degree
queue<char> qQueue for BFS
if (deg == 0) q.push(c)Add zero in-degree nodes
while (!q.empty())Process BFS
inDegree[nei]--Decrease in-degree
if (inDegree[nei] == 0) q.push(nei)Add neighbors with zero in-degree
if ((int)output.size() != (int)inDegree.size()) return ""Cycle detection
var alienOrder = function(words) {
    const graph = new Map();
    const inDegree = new Map();
    for (const w of words) {
        for (const c of w) inDegree.set(c, 0);
    }

    for (let i = 0; i < words.length - 1; i++) {
        const w1 = words[i], w2 = words[i+1];
        const minLen = Math.min(w1.length, w2.length);
        if (w1.length > w2.length && w1.slice(0, minLen) === w2.slice(0, minLen)) return "";
        for (let j = 0; j < minLen; j++) {
            if (w1[j] !== w2[j]) {
                if (!graph.has(w1[j])) graph.set(w1[j], new Set());
                if (!graph.get(w1[j]).has(w2[j])) {
                    graph.get(w1[j]).add(w2[j]);
                    inDegree.set(w2[j], inDegree.get(w2[j]) + 1);
                }
                break;
            }
        }
    }

    const queue = [];
    for (const [c, deg] of inDegree.entries()) {
        if (deg === 0) queue.push(c);
    }

    const output = [];
    while (queue.length > 0) {
        const c = queue.shift();
        output.push(c);
        if (graph.has(c)) {
            for (const nei of graph.get(c)) {
                inDegree.set(nei, inDegree.get(nei) - 1);
                if (inDegree.get(nei) === 0) queue.push(nei);
            }
        }
    }

    if (output.length !== inDegree.size) return "";
    return output.join('');
};

// Driver
const words = ["wrt", "wrf", "er", "ett", "rftt"];
console.log(alienOrder(words));
Line Notes
const inDegree = new Map()Initialize in-degree map
if (w1.length > w2.length && w1.slice(0, minLen) === w2.slice(0, minLen)) return ""Early invalid prefix detection
if (!graph.get(w1[j]).has(w2[j]))Avoid duplicate edges
inDegree.set(w2[j], inDegree.get(w2[j]) + 1)Increment in-degree
const queue = []Queue for BFS
if (deg === 0) queue.push(c)Add zero in-degree nodes
while (queue.length > 0)Process BFS
inDegree.set(nei, inDegree.get(nei) - 1)Decrease in-degree
if (inDegree.get(nei) === 0) queue.push(nei)Add neighbors with zero in-degree
if (output.length !== inDegree.size) return ""Cycle detection
⏱
Complexity
TimeO(C) total characters plus edges, efficient graph building and BFS
SpaceO(U + E) for graph and in-degree

Avoiding duplicate edges and early prefix checks reduce unnecessary work and improve correctness.

💡 This approach is the best practice for interviews, balancing clarity, correctness, and efficiency.
Interview Verdict: Accepted and recommended for interviews

This is the polished, interview-ready solution that handles all edge cases and runs efficiently.

📊
All Approaches - One-Glance Tradeoffs
💡 In interviews, always start by explaining brute force to show understanding, then implement the BFS topological sort (Kahn's algorithm) which is optimal and easier to code correctly.
ApproachTimeSpaceStack RiskReconstructUse In Interview
1. Brute Force (DFS Cycle Detection)O(C) where C is total characters plus edgesO(U + E)Yes (deep recursion possible)YesMention only - never code due to complexity and risk
2. Better (Kahn's Algorithm BFS)O(C)O(U + E)NoYesCode this approach for clarity and efficiency
3. Optimal (Kahn's Algorithm with Edge Deduplication and Early Checks)O(C)O(U + E)NoYesPreferred final solution, handles edge cases robustly
💼
Interview Strategy
💡 Use this guide to understand the problem deeply before your interview. Start by clarifying the problem, then explain the brute force approach to show understanding, and finally present the optimal solution. Practice coding and testing edge cases.

How to Present

Step 1: Clarify the problem and constraints with the interviewer.Step 2: Describe the brute force approach of comparing adjacent words and building a graph.Step 3: Explain cycle detection with DFS and its limitations.Step 4: Introduce Kahn's algorithm for BFS topological sort as the optimal solution.Step 5: Code the BFS approach carefully, handling edge cases like prefix invalidity.Step 6: Test your code with provided and edge cases.

Time Allocation

Clarify: 3min → Approach: 5min → Code: 15min → Test: 7min. Total ~30min

What the Interviewer Tests

The interviewer tests your understanding of graph construction from partial orders, cycle detection, topological sorting, and handling tricky edge cases like prefix conflicts.

Common Follow-ups

  • What if the input words list is empty or has one word? → Return all unique chars or empty string accordingly.
  • How to handle multiple valid orders? → Return any valid topological order.
  • What if the alien alphabet contains characters not in the input words? → Only consider characters present in words.
  • Can you detect cycles efficiently? → Yes, BFS topological sort detects cycles if not all nodes are processed.
💡 These follow-ups test your robustness and understanding of edge cases and problem constraints.
🔍
Pattern Recognition

When to Use

1) Given a sorted list of words in an unknown alphabet, 2) Need to find character order, 3) Partial order relations implied, 4) Detect cycles or invalid order

Signature Phrases

'Given a sorted dictionary of an alien language''Find the order of characters in the alien alphabet'

NOT This Pattern When

Sorting strings lexicographically in known alphabets or simple string comparison problems

Similar Problems

Course Schedule - both require topological sorting to detect order and cyclesSequence Reconstruction - reconstruct sequence from partial order constraints

Practice

(1/5)
1. You are given a string of digits and a target integer. You need to insert binary operators (+, -, *) between the digits to form expressions that evaluate to the target. Which algorithmic approach guarantees finding all valid expressions?
easy
A. Backtracking with enumeration of all possible operator insertions and operand splits
B. Greedy algorithm that inserts operators based on local evaluation to reach the target quickly
C. Dynamic programming that stores intermediate results for sub-expressions without operator insertion
D. Divide and conquer by splitting the string into halves and solving independently

Solution

  1. Step 1: Understand problem requirements

    The problem requires generating all valid expressions by inserting operators between digits to match a target value, which demands exploring all combinations.
  2. Step 2: Identify suitable algorithm

    Backtracking with enumeration systematically tries all splits and operator insertions, guaranteeing all solutions are found. Greedy or divide and conquer approaches fail to explore all combinations, and DP without operator insertion does not apply.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Backtracking enumerates all expressions [OK]
Hint: All expressions require exploring all splits and operators [OK]
Common Mistakes:
  • Thinking greedy can find all expressions
  • Assuming DP without operator insertion suffices
2. Given the following code snippet for MedianFinder, after adding the numbers 1, 5, and 3 in that order, what is the value returned by findMedian()?
easy
A. 4.0
B. 5.0
C. 1.0
D. 3.0

Solution

  1. Step 1: Insert 1

    small is empty, so push -1 to small. small_size=1, large_size=0.
  2. Step 2: Insert 5

    5 > -small[0] (which is 1), so push 5 to large. small_size=1, large_size=1. Heaps balanced.
  3. Step 3: Insert 3

    3 <= -small[0] (1)? No, so push 3 to large. large_size=2, small_size=1. Now large_size > small_size, so move smallest from large (3) to small (push -3). small_size=2, large_size=1.
  4. Step 4: Find median

    small_size > large_size, median is -small[0] = 3.0.
  5. Final Answer:

    Option D -> Option D
  6. Quick Check:

    Median after [1,5,3] is 3.0 [OK]
Hint: Median is top of larger heap when sizes differ [OK]
Common Mistakes:
  • Not balancing heaps after insertion
  • Returning average when sizes differ
  • Forgetting to negate values in max heap
3. Consider the following Python function implementing the single pass stack-based solution for Largest Rectangle in Histogram. What is the value of max_area returned when the input is [2, 4, 2]?
def largestRectangleArea(heights):
    stack = []
    max_area = 0
    heights.append(0)  # Sentinel to flush stack at end
    for i, h in enumerate(heights):
        while stack and heights[stack[-1]] > h:
            height = heights[stack.pop()]
            left = stack[-1] if stack else -1
            width = i - left - 1
            max_area = max(max_area, height * width)
        stack.append(i)
    heights.pop()  # Restore original list
    return max_area
easy
A. 2
B. 4
C. 6
D. 8

Solution

  1. Step 1: Trace iterations with input [2,4,2]

    Append 0 -> heights = [2,4,2,0]. Iterate i=0,h=2: stack=[0]. i=1,h=4: stack=[0,1]. i=2,h=2: pop index 1 (height=4), left=0, width=2-0-1=1, area=4*1=4, max_area=4. Then pop index 0 (height=2), left=-1, width=2-(-1)-1=2, area=2*2=4, max_area=4. Push i=2 -> stack=[2]. i=3,h=0: pop index 2 (height=2), left=-1, width=3-(-1)-1=3, area=2*3=6, max_area=6. Push i=3 -> stack=[3].
  2. Step 2: Return max_area

    max_area is 6 after processing all bars.
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    Max rectangle covers bars at indices 0,1,2 with height 2 -> area 6 [OK]
Hint: Sentinel 0 triggers final pops correctly [OK]
Common Mistakes:
  • Forgetting sentinel leads to missing last areas
  • Incorrect width calculation off by one
  • Not popping all bars when current height is smaller
4. Examine the following code snippet for computing the maximum path sum in a binary tree. Which line contains a subtle bug that can cause incorrect results?
medium
A. Line where left gain is assigned without max(..., 0)
B. Line where helper returns node.val + max(left, right)
C. Line where max_sum is updated with node.val + left + right
D. Line with base case returning 0 for null nodes

Solution

  1. Step 1: Identify that left and right gains must ignore negative sums by using max(helper(...), 0).

    Without max(..., 0), negative subtree sums reduce the path sum incorrectly.
  2. Step 2: Notice that line assigning left = helper(node.left) lacks max(..., 0), causing negative gains to propagate.

    This leads to suboptimal max_sum updates and incorrect return values.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Ignoring negative gains is a common subtle bug [OK]
Hint: Negative subtree sums must be zeroed out [OK]
Common Mistakes:
  • Not ignoring negative gains
  • Returning sum of both children to parent
  • Missing base case for null nodes
5. What is the time complexity of the divide and conquer approach to the Skyline Problem when given n buildings, and why might a common misconception about the complexity be incorrect?
medium
A. O(n log n) because the problem is divided into halves and merged efficiently
B. O(n log n) time but O(n²) space due to storing all intermediate skylines
C. O(n log n) but with additional O(n) space overhead for recursion stack making it O(n log n) space
D. O(n²) because each merge step compares all points in both skylines

Solution

  1. Step 1: Identify divide and conquer structure

    The buildings are split into halves recursively, leading to log n levels.
  2. Step 2: Analyze merge cost per level

    Each merge step processes all points in the two skylines combined, which sums to O(n) per level.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Divide and conquer with linear merge per level -> O(n log n) time [OK]
Hint: Divide and conquer merges cost O(n) per level over log n levels [OK]
Common Mistakes:
  • Assuming merge is O(n²) due to nested loops
  • Confusing recursion stack space with time complexity
  • Thinking space grows quadratically due to intermediate skylines
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard