🧠
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
- Initialize a graph with all unique characters as nodes.
- Compare each adjacent pair of words to find the first differing character and add a directed edge from the first to the second.
- Perform DFS on the graph to detect cycles and to generate a topological order.
- 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.
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
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.