package com.freetymekiyan.algorithms.level.hard;
import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
/**
* There is a new alien language which uses the latin alphabet. However, the order among letters are unknown to you. You
* receive a list of words from the dictionary, where words are sorted lexicographically by the rules of this new
* language. Derive the order of letters in this language.
* <p>
* For example,
* Given the following words in dictionary,
* <p>
* | [
* | "wrt",
* | "wrf",
* | "er",
* | "ett",
* | "rftt"
* | ]
* The correct order is: "wertf".
* <p>
* Note:
* You may assume all letters are in lowercase.
* If the order is invalid, return an empty string.
* There may be multiple valid order of letters, return any one of them is fine.
* <p>
* Company Tags: Google, Airbnb, Facebook, Twitter, Snapchat, Pocket Gems
* Tags: Graph, Topological Sort
* Similar Problems: (M) Course Schedule II
*/
public class AlienDictionary {
/**
* Graph. Topological Sort. BFS.
* Two steps:
* 1) Build a graph and in-degree from the given words.
* 2) Do topological sort.
* <p>
* Topological sort based on Kahn's algo.
* It needs a graph and each node's in-degree.
* Then first add all 0 in-degree nodes in a queue.
* While the queue is not empty, remove node from the queue and add to result order.
* Remove the node from graph as well by reducing the in-degree of connected nodes.
* If those nodes become 0 in-degree as well, add them into queue.
* When its done, check whether the result length is the same as nodes.
*/
public String alienOrder(String[] words) {
if (words == null || words.length == 0) {
return "";
}
// Init in-degree map.
Map<Character, Integer> inDeg = new HashMap<>();
for (String s : words) { // Init in-degree of all characters given to 0.
for (char c : s.toCharArray()) {
inDeg.put(c, 0);
}
}
// Build the graph and in-degree from words array.
Map<Character, Set<Character>> graph = new HashMap<>(); // Use set to avoid duplicates.
for (int i = 0; i < words.length - 1; i++) {
String cur = words[i]; // Compare current word and the next word.
String next = words[i + 1];
// Special case: when "abcee" is put before "abc".
// This case there won't be a DAG.
if (cur.length() > next.length() && cur.startsWith(next)) {
return "";
}
// Find the first different character.
for (int j = 0; j < Math.min(cur.length(), next.length()); j++) {
char c1 = cur.charAt(j);
char c2 = next.charAt(j);
if (c1 != c2) { // Create an edge from c1 -> c2.
Set<Character> set = graph.containsKey(c1) ? graph.get(c1) : new HashSet<>();
if (!set.contains(c2)) {
set.add(c2);
graph.put(c1, set); // Update graph.
inDeg.put(c2, inDeg.get(c2) + 1); // Update degree. Set makes sure in-degree count is correct.
}
break; // IMPORTANT! No need to continue.
}
}
}
// Topological Sort according to Kahn's Algo, BFS
Queue<Character> q = new ArrayDeque<>();
// First add all nodes with 0 degree to queue
for (char c : inDeg.keySet()) {
if (inDeg.get(c) == 0) {
q.offer(c);
}
}
StringBuilder res = new StringBuilder();
while (!q.isEmpty()) {
char c = q.poll();
res.append(c);
// Check the rest of the node and update in-degree
if (graph.containsKey(c)) {
for (char n : graph.get(c)) {
inDeg.put(n, inDeg.get(n) - 1);
if (inDeg.get(n) == 0) {
q.offer(n);
}
}
}
}
return res.length() == inDeg.size() ? res.toString() : ""; // Check if all nodes are in result.
}
}