• leetcode269


    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();
        }
    }
  • 相关阅读:
    Windows DLL调用实例
    DLL头文件的格式和应用
    Strategy factory
    抽象数据类型(ADT)和面向对象编程(OOP)3.5 ADT和OOP中的等价性
    抽象数据类型(ADT)和面向对象编程(OOP)3.4 面向对象的编程
    抽象数据类型(ADT)和面向对象编程(OOP)3.3 抽象数据类型
    抽象数据类型(ADT)和面向对象编程(OOP)3.2规约
    抽象数据类型(ADT)和面向对象编程(OOP)3.1数据类型和类型检查
    软件构造 消息传递
    软件构造 并发3(线程安全性)----锁定和同步
  • 原文地址:https://www.cnblogs.com/jasminemzy/p/9650777.html
Copyright © 2020-2023  润新知