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.java
For example, Given the following words in dictionary,面試
[ "wrt", "wrf", "er", "ett", "rftt" ]The correct order is:
"wertf"
.數據結構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.app
時間 O(N) 空間 O(N)函數
首先簡單介紹一下拓撲排序,這是一個可以找出有向無環圖順序的一個方法ui
假設咱們有3條邊:A->C, B->C, C->D,先將每一個節點的計數器初始化爲0。而後咱們對遍歷邊時,每遇到一個邊,把目的節點的計數器都加1。而後,咱們再遍歷一遍,找出全部計數器值仍是0的節點,這些節點就是有向無環圖的「根」。而後咱們從根開始廣度優先搜索。具體來講,搜索到某個節點時,將該節點加入結果中,而後全部被該節點指向的節點的計數器減1,在減1以後,若是某個被指向節點的計數器變成0了,那這個被指向的節點就是該節點下輪搜索的子節點。在實現的角度來看,咱們能夠用一個隊列,這樣每次從隊列頭拿出來一個加入結果中,同時把被這個節點指向的節點中計數器值減到0的節點也都加入隊列尾中。須要注意的是,若是圖是有環的,則計數器會產生斷層,即某個節點的計數器永遠沒法清零(有環意味着有的節點被多加了1,然而遍歷的時候一次只減一個1,因此致使沒法歸零),這樣該節點也沒法加入到結果中。因此咱們只要判斷這個結果的節點數和實際圖中節點數相等,就表明無環,不相等,則表明有環。this
對於這題來講,咱們首先要初始化全部節點(即字母),一個是該字母指向的字母的集合(被指向的字母在字母表中處於較後的位置),一個是該字母的計數器。而後咱們根據字典開始建圖,可是字典中並無顯示給出邊的狀況,如何根據字典建圖呢?其實邊都暗藏在相鄰兩個詞之間,好比abc
和abd
,咱們比較兩個詞的每一位,直到第一個不同的字母c
和d
,由於abd
這個詞在後面,因此d在字母表中應該是在c的後面。因此每兩個相鄰的詞都能蘊含一條邊的信息。在建圖的同時,實際上咱們也能夠計數了,對於每條邊,將較後的字母的計數器加1。計數時須要注意的是,咱們不能將一樣一條邊計數兩次,因此要用一個集合來排除已經計數過的邊。最後,咱們開始拓撲排序,從計數器爲0的字母開始廣度優先搜索。爲了找到這些計數器爲0的字母,咱們還須要先遍歷一遍全部的計數器。code
最後,根據結果的字母個數和圖中全部字母的個數,判斷時候有環便可。無環直接返回結果。排序
'a'+'b'+""
和'a'+""+'b'
是不同的,前者先算數字和,後者則是字符串拼接public class Solution { public String alienOrder(String[] words) { // 節點構成的圖 Map<Character, Set<Character>> graph = new HashMap<Character, Set<Character>>(); // 節點的計數器 Map<Character, Integer> indegree = new HashMap<Character, Integer>(); // 結果存在這個裏面 StringBuilder order = new StringBuilder(); // 初始化圖和計數器 initialize(words, graph, indegree); // 建圖並計數 buildGraphAndGetIndegree(words, graph, indegree); // 拓撲排序的最後一步,根據計數器值廣度優先搜索 topologicalSort(order, graph, indegree); // 若是大小相等說明無環 return order.length() == indegree.size() ? order.toString() : ""; } private void initialize(String[] words, Map<Character, Set<Character>> graph, Map<Character, Integer> indegree){ for(String word : words){ for(int i = 0; i < word.length(); i++){ char curr = word.charAt(i); // 對每一個單詞的每一個字母初始化計數器和圖節點 if(graph.get(curr) == null){ graph.put(curr, new HashSet<Character>()); } if(indegree.get(curr) == null){ indegree.put(curr, 0); } } } } private void buildGraphAndGetIndegree(String[] words, Map<Character, Set<Character>> graph, Map<Character, Integer> indegree){ Set<String> edges = new HashSet<String>(); for(int i = 0; i < words.length - 1; i++){ // 每兩個相鄰的詞進行比較 String word1 = words[i]; String word2 = words[i + 1]; for(int j = 0; j < word1.length() && j < word2.length(); j++){ char from = word1.charAt(j); char to = word2.charAt(j); // 若是相同則繼續,找到兩個單詞第一個不相同的字母 if(from == to) continue; // 若是這兩個字母構成的邊尚未使用過,則 if(!edges.contains(from+""+to)){ Set<Character> set = graph.get(from); set.add(to); // 將後面的字母加入前面字母的Set中 graph.put(from, set); Integer toin = indegree.get(to); toin++; // 更新後面字母的計數器,+1 indegree.put(to, toin); // 記錄這條邊已經處理過了 edges.add(from+""+to); break; } } } } private void topologicalSort(StringBuilder order, Map<Character, Set<Character>> graph, Map<Character, Integer> indegree){ // 廣度優先搜索的隊列 Queue<Character> queue = new LinkedList<Character>(); // 將有向圖的根,即計數器爲0的節點加入隊列中 for(Character key : indegree.keySet()){ if(indegree.get(key) == 0){ queue.offer(key); } } // 搜索 while(!queue.isEmpty()){ Character curr = queue.poll(); // 將隊頭節點加入結果中 order.append(curr); Set<Character> set = graph.get(curr); if(set != null){ // 對全部該節點指向的節點,更新其計數器,-1 for(Character c : set){ Integer val = indegree.get(c); val--; // 若是計數器歸零,則加入隊列中待處理 if(val == 0){ queue.offer(c); } indegree.put(c, val); } } } } }
新建一個AlienChar數據結構重寫,只用一個Map做爲Graph自身隊列
public class Solution { public String alienOrder(String[] words) { Map<Character, AlienChar> graph = new HashMap<Character, AlienChar>(); // 若是建圖失敗,好比有a->b和b->a這樣的邊,就返回false boolean isBuildSucceed = buildGraph(words, graph); if(!isBuildSucceed){ return ""; } // 在建好的圖中根據拓撲排序遍歷 String order = findOrder(graph); return order.length() == graph.size() ? order : ""; } private boolean buildGraph(String[] words, Map<Character, AlienChar> graph){ HashSet<String> visited = new HashSet<String>(); // 初始化圖,每一個字母都初始化入度爲0 initializeGraph(words, graph); for(int wordIdx = 0; wordIdx < words.length - 1; wordIdx++){ String before = words[wordIdx]; String after = words[wordIdx + 1]; Character prev = null, next = null; // 找到相鄰兩個單詞第一個不同的字母 for(int letterIdx = 0; letterIdx < before.length() && letterIdx < after.length(); letterIdx++){ if(before.charAt(letterIdx) != after.charAt(letterIdx)){ prev = before.charAt(letterIdx); next = after.charAt(letterIdx); break; } } // 若是有環,則建圖失敗 if(prev != null && visited.contains(next + "" + prev)){ return false; } // 若是這條邊沒有添加過,則在圖中加入這條邊 if(prev != null && !visited.contains(prev + "" + next)){ addEdge(prev, next, graph); visited.add(prev + "" + next); } } return true; } private void initializeGraph(String[] words, Map<Character, AlienChar> graph){ for(String word : words){ for(int idx = 0; idx < word.length(); idx++){ if(!graph.containsKey(word.charAt(idx))){ graph.put(word.charAt(idx), new AlienChar(word.charAt(idx))); } } } } private void addEdge(char prev, char next, Map<Character, AlienChar> graph){ AlienChar prevAlienChar = graph.get(prev); AlienChar nextAlienChar = graph.get(next); nextAlienChar.indegree += 1; prevAlienChar.later.add(nextAlienChar); graph.put(prev, prevAlienChar); graph.put(next, nextAlienChar); } private String findOrder(Map<Character, AlienChar> graph){ StringBuilder order = new StringBuilder(); Queue<AlienChar> queue = new LinkedList<AlienChar>(); for(Character c : graph.keySet()){ if(graph.get(c).indegree == 0){ queue.offer(graph.get(c)); } } while(!queue.isEmpty()){ AlienChar curr = queue.poll(); order.append(curr.val); for(AlienChar next : curr.later){ if(--next.indegree == 0){ queue.offer(next); } } } return order.toString(); } } class AlienChar { char val; ArrayList<AlienChar> later; int indegree; public AlienChar(char c){ this.val = c; this.later = new ArrayList<AlienChar>(); this.indegree = 0; } }
2018/10
func buildGraphAndIndegree(words []string) (map[byte][]byte, map[byte]int) { graph := make(map[byte][]byte) indegree := make(map[byte]int) for i := 1; i < len(words); i++ { prev := words[i-1] curr := words[i] for idx := range prev { if idx >= len(prev) { break } prevChar := prev[idx] if _, ok := indegree[prevChar]; !ok { indegree[prevChar] = 0 } if idx >= len(curr) { break } currChar := curr[idx] if prevChar == currChar { continue } targets := graph[prevChar] found := false for _, el := range targets { if el == currChar { found = true } } if !found { graph[prevChar] = append(targets, currChar) indegree[currChar]++ } } } return graph, indegree } func alienOrder(words []string) string { graph, indegree := buildGraphAndIndegree(words) // find the first batch of roots for topological sort var roots []byte sb := strings.Builder{} for key, value := range indegree { if value == 0 { roots = append(roots, key) sb.WriteByte(key) } } // keep sorting for len(roots) != 0 { newRoots := []byte{} for _, root := range roots { targets := graph[root] for _, target := range targets { if indegree[target] > 0 { indegree[target]-- } if indegree[target] == 0 { newRoots = append(newRoots, target) } } } for _, root := range newRoots { sb.WriteByte(root) } roots = newRoots } if sb.Len() == len(indegree) { return sb.String() } return "" }