资讯专栏INFORMATION COLUMN

[Leetcode] Alien Dictionary 外文字典

pkhope / 1929人阅读

摘要:拓扑排序复杂度时间空间思路首先简单介绍一下拓扑排序,这是一个能够找出有向无环图顺序的一个方法假设我们有条边,先将每个节点的计数器初始化为。最后,我们开始拓扑排序,从计数器为的字母开始广度优先搜索。

Alien Dictionary

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.

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.

拓扑排序 复杂度

时间 O(N) 空间 O(N)

思路

首先简单介绍一下拓扑排序,这是一个能够找出有向无环图顺序的一个方法

假设我们有3条边:A->C, B->C, C->D,先将每个节点的计数器初始化为0。然后我们对遍历边时,每遇到一个边,把目的节点的计数器都加1。然后,我们再遍历一遍,找出所有计数器值还是0的节点,这些节点就是有向无环图的“根”。然后我们从根开始广度优先搜索。具体来说,搜索到某个节点时,将该节点加入结果中,然后所有被该节点指向的节点的计数器减1,在减1之后,如果某个被指向节点的计数器变成0了,那这个被指向的节点就是该节点下轮搜索的子节点。在实现的角度来看,我们可以用一个队列,这样每次从队列头拿出来一个加入结果中,同时把被这个节点指向的节点中计数器值减到0的节点也都加入队列尾中。需要注意的是,如果图是有环的,则计数器会产生断层,即某个节点的计数器永远无法清零(有环意味着有的节点被多加了1,然而遍历的时候一次只减一个1,所以导致无法归零),这样该节点也无法加入到结果中。所以我们只要判断这个结果的节点数和实际图中节点数相等,就代表无环,不相等,则代表有环。

对于这题来说,我们首先要初始化所有节点(即字母),一个是该字母指向的字母的集合(被指向的字母在字母表中处于较后的位置),一个是该字母的计数器。然后我们根据字典开始建图,但是字典中并没有显示给出边的情况,如何根据字典建图呢?其实边都暗藏在相邻两个词之间,比如abcabd,我们比较两个词的每一位,直到第一个不一样的字母cd,因为abd这个词在后面,所以d在字母表中应该是在c的后面。所以每两个相邻的词都能蕴含一条边的信息。在建图的同时,实际上我们也可以计数了,对于每条边,将较后的字母的计数器加1。计数时需要注意的是,我们不能将同样一条边计数两次,所以要用一个集合来排除已经计数过的边。最后,我们开始拓扑排序,从计数器为0的字母开始广度优先搜索。为了找到这些计数器为0的字母,我们还需要先遍历一遍所有的计数器。

最后,根据结果的字母个数和图中所有字母的个数,判断时候有环即可。无环直接返回结果。

注意

因为这题代码很冗长,面试的时候最好都把几个大步骤都写成子函数,先完成主函数,再实现各个子函数,比如初始化图,建图,加边,排序,都可以分开

要先对字典里所有存在的字母初始化入度为0,否则之后建图可能会漏掉一些没有入度的字母

"a"+"b"+"""a"+""+"b"是不一样的,前者先算数字和,后者则是字符串拼接

因为字典里有重复的边,所有要先判断,已经添加过的边不要重复添加

代码
    public class Solution {
        public String alienOrder(String[] words) {
            // 节点构成的图
            Map> graph = new HashMap>();
            // 节点的计数器
            Map indegree = new HashMap();
            // 结果存在这个里面
            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> graph, Map 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());
                    }
                    if(indegree.get(curr) == null){
                        indegree.put(curr, 0);
                    }
                }
            }
        }
        
        private void buildGraphAndGetIndegree(String[] words, Map> graph, Map indegree){
            Set edges = new HashSet();
            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 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> graph, Map indegree){
            // 广度优先搜索的队列
            Queue queue = new LinkedList();
            // 将有向图的根,即计数器为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 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 graph = new HashMap();
        // 如果建图失败,比如有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 graph){
        HashSet visited = new HashSet();
        // 初始化图,每个字母都初始化入度为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 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 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 graph){
        StringBuilder order = new StringBuilder();
        Queue queue = new LinkedList();
        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 later;
    int indegree;
    public AlienChar(char c){
        this.val = c;
        this.later = new ArrayList();
        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 ""
}

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/64606.html

相关文章

  • [LeetCode] 953. Verifying an Alien Dictionary

    Problem In an alien language, surprisingly they also use english lowercase letters, but possibly in a different order. The order of the alphabet is some permutation of lowercase letters. Given a seque...

    ghnor 评论0 收藏0
  • Leetcode PHP题解--D61 953. Verifying an Alien Dictio

    摘要:题目链接题目分析给定一个单词数组和一个字符串,判断给定的数组是否满足给定字符串的顺序。思路按给定字符串,替换成正常顺序的单词。再判断之前和之后的数组是否相同。最终代码若觉得本文章对你有用,欢迎用爱发电资助。 D61 953. Verifying an Alien Dictionary 题目链接 953. Verifying an Alien Dictionary 题目分析 给定一个单词...

    sshe 评论0 收藏0
  • Alien Dictionary

    摘要:题目链接图用,和题类似。要找到所有字母的,之后用存下入度为的字母,然后输出。要记录下每个字母对应的所有字母,防止重复。求的过程可以用,从所有单词的开始,对不同的字母存入度,相同的去下一层。 Alien Dictionary 题目链接:https://leetcode.com/problems... 图用topological sort,和course schedule题类似。要找到所有...

    gaomysion 评论0 收藏0
  • Python学习之路5-字典

    摘要:本章主要介绍字典的概念,基本操作以及一些进阶操作。使用字典在中,字典是一系列键值对。中用花括号来表示字典。代码定义空字典的语法结果如果要修改字典中的值,只需通过键名访问就行。 《Python编程:从入门到实践》笔记。本章主要介绍字典的概念,基本操作以及一些进阶操作。 1. 使用字典(Dict) 在Python中,字典是一系列键值对。每个键都与一个值相关联,用键来访问值。Python中用...

    NicolasHe 评论0 收藏0
  • [Leetcode] Word Break 单词分解

    摘要:所以只要验证满足这个条件,我们则可以确定这个较长的字符串也是可分解的。同时,我们用数组记录下字符串长度递增时可分解的情况,以供之后使用,避免重复计算。当遍历完这个词典并找出所有以第一个字母开头的词以后,我们进入下一轮搜索。 Word Break I Given a string s and a dictionary of words dict, determine if s can ...

    Ververica 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<