Welcome to Subscribe On Youtube
Question
Formatted question description: https://leetcode.ca/all/269.html
There is a new alien language that uses the English alphabet. However, the order among the letters is unknown to you.
You are given a list of strings words
from the alien language's dictionary, where the strings in words
are sorted lexicographically by the rules of this new language.
Return a string of the unique letters in the new alien language sorted in lexicographically increasing order by the new language's rules. If there is no solution, return ""
. If there are multiple solutions, return any of them.
Example 1:
Input: words = ["wrt","wrf","er","ett","rftt"] Output: "wertf"
Example 2:
Input: words = ["z","x"] Output: "zx"
Example 3:
Input: words = ["z","x","z"]
Output: ""
Explanation: The order is invalid, so return ""
.
Constraints:
1 <= words.length <= 100
1 <= words[i].length <= 100
words[i]
consists of only lowercase English letters.
Algorithm
This problem is a variant of the classical topological sorting
problem, where you’re given a partial order of elements and asked to extend it to a total order.
Overview
- Graph Representation: The problem is modeled as a directed graph where each node represents a character, and an edge from node
u
to nodev
implies thatu
comes beforev
in the alien language. The goal is to find a topological ordering of the graph’s nodes, which represents the alphabet of the alien language.
_buildGraph
Method
-
Node Initialization: Initializes
inDegree
for all characters in all words to 0 and prepares the graph.inDegree
counts the number of edges pointing to a node, which is used to identify nodes with no incoming edges. -
Graph Construction: By comparing adjacent words in the list, it determines the order between different characters and constructs the graph accordingly. When the first non-matching characters between two words are found, an edge is added from the character in the first word to the character in the second word. This is because the sorted list of words implies that characters in earlier words precede those in later words.
-
Edge Addition and
inDegree
Update: If characterv
is not already in the adjacency list ofu
(graph[u]
), it addsv
tou
’s list and incrementsv
’sinDegree
. This maintains the count of how many characters come beforev
. -
Invalid Order Detection: If a longer word comes before a shorter word and they match up to the length of the shorter word, the order is invalid (e.g., “ab” before “a”), and the graph is cleared to signal an invalid configuration.
_topology
Method
-
Topological Sort: Implements topological sorting using Kahn’s algorithm. It starts with characters that have an
inDegree
of 0 (i.e., characters that don’t have any characters preceding them) and adds them to a queue. While the queue is not empty, it removes a character from the queue, appends it to theresult
string (since this character has no preceding characters or all of them have already been added to the result), and decreases theinDegree
of its adjacent characters. If any adjacent character’sinDegree
becomes 0, it’s added to the queue. -
Cycle Detection: If, after processing all characters, there are still characters with non-zero
inDegree
, it means there’s a cycle in the graph, and no valid ordering exists. This scenario returns an empty string. -
Result Validation: Finally, it checks if the length of the
result
string matches the number of unique characters ininDegree
. If not, it means not all characters were accounted for, possibly due to an invalid input, and it returns an empty string if such a case is detected.
Conclusion
This solution effectively builds a graph to represent the partial ordering of characters inferred from the input list of words, then performs a topological sort on this graph to determine the characters’ order in the alien language. The topological sort also incorporates checks for cycles and invalid configurations, which would indicate that no valid ordering exists.
Code
-
import java.util.*; public class Alien_Dictionary { public static void main(String[] args) { Alien_Dictionary out = new Alien_Dictionary(); Solution s = out.new Solution(); Solution_bfs s2 = out.new Solution_bfs(); String[] words = new String[] { "wrt", "wrf", "er", "ett", "rftt" }; System.out.println(s.alienOrder(words)); System.out.println(s2.alienOrder(words)); } // ref: http://buttercola.blogspot.com/2015/09/leetcode-alien-dictionary.html public class Solution_bfs { /** * @param words: a list of words * @return: a string which is correct order */ public String alienOrder(String[] words) { // Write your code here if (words == null || words.length == 0) { return ""; } // step 1: construct the graph // Map<Character, List<Character>> adjMap = new HashMap<>(); constructGraph(words, adjMap); int numNodes = adjMap.size(); StringBuilder result = new StringBuilder(); // toplogical sorting // Map<Character, Integer> indegreeMap = new HashMap<>(); for (Character node : adjMap.keySet()) { indegreeMap.put(node, 0); } for (Character node : adjMap.keySet()) { for (Character neighbor : adjMap.get(node)) { int indegree = indegreeMap.get(neighbor); indegree += 1; indegreeMap.put(neighbor, indegree); } } // start from indegree=0 Queue<Character> queue = new PriorityQueue<>(); for (Character node : indegreeMap.keySet()) { if (indegreeMap.get(node) == 0) { // starting node, can only be one, cannot be 2 starting with 0 indegree queue.offer(node); } } while (!queue.isEmpty()) { char curNode = queue.poll(); result.append(curNode); for (char neighbor : adjMap.get(curNode)) { int indegree = indegreeMap.get(neighbor); indegree -= 1; indegreeMap.put(neighbor, indegree); // @note: key is here. // for A->B, B->C, A-C: C will not be counted until its indgree is 0 if (indegree == 0) { queue.offer(neighbor); } } } if (result.length() < numNodes) { return ""; } return result.toString(); } private void constructGraph(String[] words, Map<Character, List<Character>> adjMap) { // construct nodes for (String word : words) { for (Character c : word.toCharArray()) { adjMap.put(c, new ArrayList<>()); // c to all its next } } // construct edges for (int i = 1; i < words.length; i++) { String prev = words[i - 1]; String curr = words[i]; for (int j = 0; j < prev.length() && j < curr.length(); j++) { if (prev.charAt(j) != curr.charAt(j)) { adjMap.get(prev.charAt(j)).add(curr.charAt(j)); break; } } } } } public class Solution { // dfs public String alienOrder(String[] words) { // Step 1: build the graph Map<Character, Set<Character>> graph = new HashMap<>(); for (int i = 0; i < words.length; i++) { String currentWord = words[i]; for (int j = 0; j < currentWord.length(); j++) { if (!graph.containsKey(currentWord.charAt(j))) { graph.put(currentWord.charAt(j), new HashSet<>()); } } if (i > 0) { connectGraph(graph, words[i - 1], currentWord); } } // Step 2: topological sorting StringBuffer sb = new StringBuffer(); Map<Character, Integer> visited = new HashMap<Character, Integer>(); // mark as visited: visited.put(vertexId, -1); for (Map.Entry<Character, Set<Character>> entry: graph.entrySet()) { char vertexId = entry.getKey(); if (!topologicalSort(vertexId, graph, sb, visited)) { return ""; } } return sb.toString(); } private void connectGraph(Map<Character, Set<Character>> graph, String prev, String curr) { if (prev == null || curr == null) { return; } int len = Math.min(prev.length(), curr.length()); for (int i = 0; i < len; i++) { char p = prev.charAt(i); char q = curr.charAt(i); if (p != q) { // so if same duplicated work, will not reach here and not update graph if (!graph.get(p).contains(q)) { graph.get(p).add(q); } break; } } } private boolean topologicalSort( char vertexId, Map<Character, Set<Character>> graph, StringBuffer sb, Map<Character, Integer> visited ) { if (visited.containsKey(vertexId)) { // visited if (visited.get(vertexId) == -1) { // -1 meaning visited, cycle found return false; } // already in the list if (visited.get(vertexId) == 1) { return true; } } visited.put(vertexId, -1); // mark as visited Set<Character> neighbors = graph.get(vertexId); for (char neighbor : neighbors) { if (!topologicalSort(neighbor, graph, sb, visited)) { return false; } } sb.insert(0, vertexId); visited.put(vertexId, 1); // restore visited return true; } } } ////// class Solution { public String alienOrder(String[] words) { boolean[][] g = new boolean[26][26]; boolean[] s = new boolean[26]; int cnt = 0; int n = words.length; for (int i = 0; i < n - 1; ++i) { for (char c : words[i].toCharArray()) { if (cnt == 26) { break; } c -= 'a'; if (!s[c]) { ++cnt; s[c] = true; } } int m = words[i].length(); for (int j = 0; j < m; ++j) { if (j >= words[i + 1].length()) { return ""; } char c1 = words[i].charAt(j), c2 = words[i + 1].charAt(j); if (c1 == c2) { continue; } if (g[c2 - 'a'][c1 - 'a']) { return ""; } g[c1 - 'a'][c2 - 'a'] = true; break; } } for (char c : words[n - 1].toCharArray()) { if (cnt == 26) { break; } c -= 'a'; if (!s[c]) { ++cnt; s[c] = true; } } int[] indegree = new int[26]; for (int i = 0; i < 26; ++i) { for (int j = 0; j < 26; ++j) { if (i != j && s[i] && s[j] && g[i][j]) { ++indegree[j]; } } } Deque<Integer> q = new LinkedList<>(); for (int i = 0; i < 26; ++i) { if (s[i] && indegree[i] == 0) { q.offerLast(i); } } StringBuilder ans = new StringBuilder(); while (!q.isEmpty()) { int t = q.pollFirst(); ans.append((char) (t + 'a')); for (int i = 0; i < 26; ++i) { if (i != t && s[i] && g[t][i]) { if (--indegree[i] == 0) { q.offerLast(i); } } } } return ans.length() < cnt ? "" : ans.toString(); } }
-
// OJ: https://leetcode.com/problems/alien-dictionary/ // Time: O(N) // Space: O(1) class Solution { public: string alienOrder(vector<string>& A) { unordered_map<int, unordered_set<int>> G; int indegree[26] = {}; for (auto &s : A) { for (char c : s) G[c - 'a'] = {}; } for (int i = 1; i < A.size(); ++i) { int j = 0; for (; j < min(A[i - 1].size(), A[i].size()); ++j) { if (A[i - 1][j] == A[i][j]) continue; G[A[i - 1][j] - 'a'].insert(A[i][j] - 'a'); break; } if (j == A[i].size() && j < A[i - 1].size()) return ""; } for (auto &[from, tos] : G) { for (int to : tos) { indegree[to]++; } } queue<int> q; for (int i = 0; i < 26; ++i) { if (G.count(i) && indegree[i] == 0) q.push(i); } string ans; while (q.size()) { int u = q.front(); q.pop(); ans += u + 'a'; for (int v : G[u]) { if (--indegree[v] == 0) q.push(v); } } return ans.size() == G.size() ? ans : ""; } };
-
''' >>> a = ['a','b','c'] >>> zip(a, a[1:]) [('a', 'b'), ('b', 'c')] # same as pairwise() >>> from itertools import pairwise >>> pairwise(a) <itertools.pairwise object at 0x108458730> >>> list(pairwise(a)) [('a', 'b'), ('b', 'c')] ''' ''' >>> inDegree = {'a':11, 'b':22, 'c':33} >>> [print(c) for c in inDegree.keys()] a b c [None, None, None] # same as for c in inDegree.keys() >>> [print(c) for c in inDegree] a b c [None, None, None] >>> [print(c) for c in inDegree.values()] 11 22 33 [None, None, None] >>> [print(c) for c in inDegree.items()] ('a', 11) ('b', 22) ('c', 33) [None, None, None] ''' from collections import defaultdict, deque from typing import Dict, List, Set class Solution: def alienOrder(self, words: List[str]) -> str: graph = defaultdict(set) inDegree = defaultdict(int) self._buildGraph(graph, words, inDegree) return self._topology(graph, inDegree) def _buildGraph(self, graph: Dict[str, Set[str]], words: List[str], inDegree: Dict[str, int]) -> None: # Create a node for each character in each word for word in words: for c in word: inDegree[c] = 0 # necessary for final char counting for first, second in zip(words, words[1:]): # or pairwise(words) length = min(len(first), len(second)) for j in range(length): u = first[j] v = second[j] if u != v: if v not in graph[u]: graph[u].add(v) inDegree[v] += 1 break # Later characters' order is meaningless if j == length - 1 and len(first) > len(second): # If 'ab' comes before 'a', it's an invalid order graph.clear() return def _topology(self, graph: Dict[str, Set[str]], inDegree: Dict[str, int]) -> str: result = '' q = deque([c for c in inDegree if inDegree[c] == 0]) while q: u = q.popleft() result += u for v in graph[u]: inDegree[v] -= 1 if inDegree[v] == 0: q.append(v) # If there are remaining characters in inDegree, it means there's a cycle if any(inDegree.values()): return '' # Words = ['z', 'x', 'y', 'x'] return result if len(result) == len(indegree) else '' ############ import collections class Node(object): def __init__(self, val): self.val = val self.neighbors = [] def connect(self, node): self.neighbors.append(node) def getNbrs(self): return self.neighbors class Solution(object): def alienOrder(self, words): """ :type words: List[str] :rtype: str """ def dfs(root, graph, visited): visited[root] = 1 for nbr in graph[root].getNbrs(): if visited[nbr.val] == 0: if not dfs(nbr.val, graph, visited): return False elif visited[nbr.val] == 1: return False visited[root] = 2 self.ans += root return True self.ans = "" graph = {} visited = collections.defaultdict(int) self.topNum = 0 for i in range(0, len(words) - 1): a = words[i] b = words[i + 1] i = 0 while i < len(a) and i < len(b): if a[i] != b[i]: nodeA = nodeB = None if a[i] not in graph: nodeA = Node(a[i]) graph[a[i]] = nodeA else: nodeA = graph[a[i]] if b[i] not in graph: nodeB = Node(b[i]) graph[b[i]] = nodeB else: nodeB = graph[b[i]] nodeA.connect(nodeB) break i += 1 if i < len(a) and i >= len(b): return "" for c in graph: if visited[c] == 0: if not dfs(c, graph, visited): return "" unUsedSet = set() for word in words: for c in word: unUsedSet.add(c) for c in unUsedSet: if c not in graph: self.ans += c return self.ans[::-1]