Graph
, Topological Sorting
Hard
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 non-empty words from the dictionary, where words are sorted lexicographically by the rules of this new language . Derive the order of letters in this language.
Example 1:
Copy Input:
[
"wrt",
"wrf",
"er",
"ett",
"rftt"
]
Output: "wertf"
Example 2:
Copy Input:
[
"z",
"x"
]
Output: "zx"
Example 3:
Copy Input:
[
"z",
"x",
"z"
]
Output:""
Explanation: The order is invalid, so return "".
Note:
You may assume all letters are in lowercase.
You may assume that if a is a prefix of b, then a must appear before b in the given dictionary.
If the order is invalid, return an empty string.
There may be multiple valid order of letters, return any one of them is fine.
Analysis & Solution
问题转化 :alien单词字母表的先后顺序转化为依赖关系,那么这个字母表顺序就可以通过拓扑排序来得到。
Topological Sorting - BFS
通过input words建立邻接表,记录In-degree的数目,从in-degree为0的节点(字母)开始,放入Queue中,进行BFS。
接下来的就是用Kahn's aglorithm来做topological sort。
注意的要点:
如何将alien dictionary转化为邻接表
循环words[],words[i], words[i + 1]从头进行字符比较,如果出现不一样的字符 ch1 != ch2,说明存在lexicographical的关系(ch1 在 ch2之前),就可以记录ch2在ch1的邻接表中,并更新ch2的indegree加1。 因为从topological的角度,就是ch1依赖ch2,所以是ch2的indegree + 1。
如何初始化indegree的计数
有个tricky的地方在于如果用int indegree[]来记录,因为数组初始化默认都为0,所以不能仅仅凭借indegree[i - 'a']判断是否入度降为0,而需要额外辅助的set,记录在alien dictionary中出现过的字符。
或者用一个HashMap<Character, Integer> 进行记录,这样既可以计数indegree,也满足只记录alien dict出现的字符
何时更新indegree的计数
每次在queue中poll()之后,相当于要对这个ch对应的所有依赖的字符更新indegree,也就是在adjacency list邻接表中遍历对应的字符,再更新indegree的记录-1.
如何判断是否存在topological sorting
如果存在topological sorting,那么最后result的长度应该等于alien dictionary的key的个数
或者检测indegree hashmap中的每个字符indegree计数,如果有一个不为0,则说明不满足
代码:
Copy class Solution {
public String alienOrder ( String [] words) {
String result = "" ;
if (words == null || words . length == 0 ) {
return result;
}
HashMap < Character , HashSet < Character >> adj = new HashMap <>();
HashMap < Character , Integer > indegree = new HashMap <>();
// Initiate indegree for characters in the alien dict
for ( String word : words) {
for ( char c : word . toCharArray ()) {
indegree . put (c , 0 );
}
}
// Build graph with adjacency list; update indegree
for ( int i = 0 ; i < words . length - 1 ; i ++ ) {
int j = 0 ;
while (j < words[i] . length () && j < words[i + 1 ] . length ()) {
char a = words[i] . charAt (j);
char b = words[i + 1 ] . charAt (j);
if (a != b) {
HashSet < Character > set = new HashSet <>();
if ( adj . containsKey (a)) {
set = adj . get (a);
}
if ( ! set . contains (b)) {
set . add (b);
adj . put (a , set);
indegree . put (b , indegree . get (b) + 1 );
}
break ;
}
j ++ ;
}
}
// Initiate queue for topological sorting
Queue < Character > q = new LinkedList <>();
for ( Character c : indegree . keySet ()) {
if ( indegree . get (c) == 0 ) {
q . offer (c);
}
}
// Topological Sorting
while ( ! q . isEmpty ()) {
char ch = q . poll ();
result += ch;
if ( adj . containsKey (ch)) {
for ( char c : adj . get (ch)) {
indegree . put (c , indegree . get (c) - 1 );
if ( indegree . get (c) == 0 ) {
q . offer (c);
}
}
}
}
// No valid topological sorting
if ( result . length () != indegree . size ()) {
return "" ;
}
return result;
}
}
Topological Sorting - DFS
by @yavinci:
The key to this problem:
A topological ordering is possible if and only if the graph has no directed cycles
Let's build a graph and perform a DFS. The following states made things easier.
visited[i] = -1
. Not even exist.
visited[i] = 0
. Exist. Non-visited.
visited[i] = 1
. Visiting.
Copy private final int N = 26 ;
public String alienOrder( String [] words) {
boolean [][] adj = new boolean [N][N];
int [] visited = new int [N];
buildGraph(words , adj , visited) ;
StringBuilder sb = new StringBuilder() ;
for ( int i = 0 ; i < N; i ++ ) {
if (visited[i] == 0 ) { // unvisited
if ( ! dfs(adj , visited , sb , i) ) return "" ;
}
}
return sb . reverse () . toString ();
}
public boolean dfs( boolean [][] adj , int [] visited , StringBuilder sb , int i) {
visited[i] = 1 ; // 1 = visiting
for ( int j = 0 ; j < N; j ++ ) {
if (adj[i][j]) { // connected
if (visited[j] == 1 ) return false ; // 1 => 1, cycle
if (visited[j] == 0 ) { // 0 = unvisited
if ( ! dfs(adj , visited , sb , j) ) return false ;
}
}
}
visited[i] = 2 ; // 2 = visited
sb . append (( char ) (i + 'a' ));
return true ;
}
public void buildGraph( String [] words , boolean [][] adj , int [] visited) {
Arrays . fill (visited , - 1 ); // -1 = not even existed
for ( int i = 0 ; i < words . length ; i ++ ) {
for ( char c : words[i] . toCharArray ()) visited[c - 'a' ] = 0 ;
if (i > 0 ) {
String w1 = words[i - 1 ] , w2 = words[i];
int len = Math . min ( w1 . length () , w2 . length ());
for ( int j = 0 ; j < len; j ++ ) {
char c1 = w1 . charAt (j) , c2 = w2 . charAt (j);
if (c1 != c2) {
adj[c1 - 'a' ][c2 - 'a' ] = true ;
break ;
}
}
}
}
}
Another DFS - More Modularized
Copy class Solution {
public String alienOrder ( String [] dict) {
// corner case
if (dict == null || dict . length == 0 ) {
return new String() ;
}
if ( dict . length == 1 ) {
return dict[ 0 ];
}
Map < Character , Set < Character >> graph = initialGraph(dict) ;
return topoOrder(graph) ;
}
private Map < Character , Set < Character >> initialGraph ( String [] dict) {
Map < Character , Set < Character >> graph = new HashMap <>();
// each adjacent pairs
for ( int i = 1 ; i < dict . length ; i ++ ) {
String one = dict[i - 1 ];
String two = dict[i];
addEdge(one , two , graph) ;
}
return graph;
}
private void addEdge ( String one , String two ,
Map < Character , Set < Character >> graph) {
for ( int i = 0 ; i < one . length (); i ++ ) {
graph . putIfAbsent ( one . charAt (i) , new HashSet < Character >());
}
for ( int i = 0 ; i < two . length (); i ++ ) {
graph . putIfAbsent ( two . charAt (i) , new HashSet < Character >());
}
for ( int i = 0 ; i < one . length () && i < two . length (); i ++ ) {
if ( one . charAt (i) != two . charAt (i)) {
graph . get ( one . charAt (i)) . add ( two . charAt (i));
return ;
}
}
}
private String topoOrder ( Map < Character , Set < Character >> graph) {
StringBuilder sb = new StringBuilder() ;
// recording visit status, -1 is visiting , 1 is visited
Map < Character , Integer > visited = new HashMap <>();
for ( Character v : graph . keySet ()) {
visited . put (v , 0 );
}
for ( Character v : graph . keySet ()) {
if ( traverse(graph , v , visited , sb) ) {
return new String() ;
}
}
return sb . reverse () . toString ();
}
// traverse graph from v, return true if there is cycle.
// also record the order of visited order
private boolean traverse ( Map < Character , Set < Character >> graph , Character v ,
Map < Character , Integer > visited , StringBuilder sb) {
// base case
if ( visited . get (v) == - 1 ) {
return true ;
}
if ( visited . get (v) == 1 ) {
return false ;
}
// mark visiting
visited . put (v , - 1 );
for ( Character nei : graph . get (v)) {
if ( traverse(graph , nei , visited , sb) ) {
return true ;
}
}
visited . put (v , 1 );
sb . append (v);
return false ;
}
}