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:
Input:[ "wrt","wrf","er","ett","rftt"]
Output: “wertf"
Example 2:
Input:["z","x"]
Output: "zx"
Example 3:
Input:["z", "x", "z"]
Output: ""
Explanation: The order is invalid, so return "".
拓扑排序。
general流程:
1. 统计所有点的入度,并初始化拓扑序列为空。
2. 将所有入度为 0 的点,也就是那些没有任何依赖的点,放到宽度优先搜索的队列中
3. 将队列中的点一个一个的释放出来,放到拓扑序列中,每次释放出某个点 A 的时候,就访问 A 的相邻点(所有A指向的点),并把这些点的入度减去 1。
4. 如果发现某个点的入度被减去 1 之后变成了 0,则放入队列中。
5. 直到队列为空时,算法结束,
(如果关系是先a后b,那图也就是map里要让a指向b,入度里要让b的增加)
本题流程:
1.初始化图和入度,让所有字符都有所记录,哪怕空荡荡的默认值。
2.详细计算图和入度。注意最多只对比前后字符串共享部分。
3.开始BFS,每从q里拿到一个字符就推入sb。
4.检查sb的length()是不是和所有出现过的字符数一样,从而知道有没有出现循环依赖导致拓扑排序不合格。
5.返回sb.toString()。
细节:
1.把对图的初始化和详细计算分开。因为详细计算内部只遍历前后两个字符串里短的那部分,实际上两个字符串后面长出来没有对比到的那部分,里面可能还有部分indegree == 0的字符。这些字符也要让他们的入度为0从而排到最前面,而且也要让他们的map取得到而不是null,从而避免之后BFS时for(char next : map.get(这些c))时,导致iterator 一个null对象了编译报错。
2.小心重复,也就是a->b出现多次的情况。如果map里用的是set数据结构来记录依赖关系,那a->b在这里只会记录一次的,如果你入度是每次看到a->b都加一次,那肯定就不一致出错了,之后BFS时无法正确地让indegree降到0,解决方法是只在第一次看到a->b的时候加入度。另外一个解决方案是map里用list数据结构记录依赖关系,看到所有a->b都增加list也增加indegree,缺陷是这种方式有冗余,时间复杂度会稍高。
3.注意本题每相邻两行只可以得到一个信息:第一个不相等的char之间的依赖关系。那么你在看到两个字符不等,处理好map和indegree的更新后,应该立刻break,不能继续比后面的字符,因为那些是无效信息了。
4.最后记得确认有没有循环依赖。
我的实现:
class Solution { public String alienOrder(String[] words) { Map<Character, List<Character>> map = new HashMap<>(); Map<Character, Integer> indegree = new HashMap<>(); for (String word : words) { for (char c : word.toCharArray()) { // P1: 解决下面循环里,两个字符串后面长出来没有对比到的那部分,这里面可能还有部分indegree == 0的字符没加进indegree。而且map不给的话,之后BFS拿这些入度为0而且从来没给依赖关系的点,会出现空指针情况。 indegree.putIfAbsent(c, 0); map.putIfAbsent(c, new ArrayList<Character>()); } } for (int i = 0; i < words.length - 1; i++) { for (int j = 0; j < Math.min(words[i].length(), words[i + 1].length()); j++) { if (words[i].charAt(j) != words[i + 1].charAt(j)) { // P1: indegree不能和graph一起做,因为graph只遍历两个字符串里短的那部分, char c1 = words[i].charAt(j); char c2 = words[i + 1].charAt(j); // P2: 小心a->b出现多次的情况,如果map内部嵌套的是set,不能出现第二次a->b的时候也无脑加入度,一定要让只有第一次出现a->b的时候加一次有效入度。因为你set没能再加一次,入度多加了的话,之后BFS的时候你的入度就减不到0了。另一个解决方案是map内部嵌套list,不过有冗余,时间复杂度高一点。 if (map.get(c1).add(c2)) { indegree.put(c2, indegree.get(c2) + 1); } // map.get(c1).add(c2); // indegree.put(c2, indegree.get(c2) + 1); //P3: 注意对比完一个就结束了!后续不能接着比的!两个字符串只得一个有效信息。 break; } } } Queue<Character> q = new LinkedList<>(); for (char c : indegree.keySet()) { if (indegree.get(c) == 0) { q.offer(c); } } StringBuilder sb = new StringBuilder(); while (!q.isEmpty()) { char crt = q.poll(); sb.append(crt); for (char next : map.get(crt)) { indegree.put(next, indegree.get(next) - 1); if (indegree.get(next) == 0) { q.offer(next); } } } // P4:要确认一下有没有出现循环依赖。 if (sb.length() != indegree.size()) { return ""; } else { return sb.toString(); } } }
九章实现:
/** * 本参考程序来自九章算法,由 @令狐冲 提供。版权所有,转发请注明出处。 * - 九章算法致力于帮助更多中国人找到好的工作,教师团队均来自硅谷和国内的一线大公司在职工程师。 * - 现有的面试培训课程包括:九章算法班,系统设计班,算法强化班,Java入门与基础算法班,Android 项目实战班, * - Big Data 项目实战班,算法面试高频题班, 动态规划专题班 * - 更多详情请见官方网站:http://www.jiuzhang.com/?source=code */ class Solution { public String alienOrder(String[] words) { Map<Character, Set<Character>> graph = constructGraph(words); return topologicalSorting(graph); } private Map<Character, Set<Character>> constructGraph(String[] words) { Map<Character, Set<Character>> graph = new HashMap<>(); // create nodes for (int i = 0; i < words.length; i++) { for (int j = 0; j < words[i].length(); j++) { char c = words[i].charAt(j); if (!graph.containsKey(c)) { graph.put(c, new HashSet<Character>()); } } } // create edges for (int i = 0; i < words.length - 1; i++) { int index = 0; while (index < words[i].length() && index < words[i + 1].length()) { if (words[i].charAt(index) != words[i + 1].charAt(index)) { graph.get(words[i].charAt(index)).add(words[i + 1].charAt(index)); break; } index++; } } return graph; } private Map<Character, Integer> getIndegree(Map<Character, Set<Character>> graph) { Map<Character, Integer> indegree = new HashMap<>(); for (Character u : graph.keySet()) { indegree.put(u, 0); } for (Character u : graph.keySet()) { for (Character v : graph.get(u)) { indegree.put(v, indegree.get(v) + 1); } } return indegree; } private String topologicalSorting(Map<Character, Set<Character>> graph) { Map<Character, Integer> indegree = getIndegree(graph); // as we should return the topo order with lexicographical order // we should use PriorityQueue instead of a FIFO Queue Queue<Character> queue = new PriorityQueue<>(); for (Character u : indegree.keySet()) { if (indegree.get(u) == 0) { queue.offer(u); } } StringBuilder sb = new StringBuilder(); while (!queue.isEmpty()) { Character head = queue.poll(); sb.append(head); for (Character neighbor : graph.get(head)) { indegree.put(neighbor, indegree.get(neighbor) - 1); if (indegree.get(neighbor) == 0) { queue.offer(neighbor); } } } if (sb.length() != indegree.size()) { return ""; } return sb.toString(); } }