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 "" }