Monday, August 14, 2017

269. Alien Dictionary

Version #3 Char Array
看到discuss里面一个时间空间都效率比较高的方法,但是有点hard coded,没那么直观
因为确定只有26个字母,所以可以直接开一个char[26]
Let's build a graph and perform a DFS. The following states made things easier.
  1. visited[i] = -1. Not even exist.
  2. visited[i] = 0. Exist. Non-visited.
  3. visited[i] = 1. Visiting.
  4. visited[i] = 2. Visited.
用上面这四个状态代替了path,visited,hashmap这一堆东西,感觉速度会很快,有种黑科技的感觉

五刷 07/2022
Version #1 DFS Topological Sort
DFS Topological Sort的难点就是不仅需要一个全局的visited set还需要一个path set来判断有没有环
Time O(C) - total length of all words
Space O(1)
Runtime: 5 ms, faster than 83.85% of Java online submissions for Alien Dictionary.
Memory Usage: 43.3 MB, less than 22.79% of Java online submissions for Alien Dictionary.


class Solution {
    public String alienOrder(String[] words) {
        Map<Character, Set<Character>> graph = new HashMap<>();
        putAllChars(words[0], graph);
        for (int i = 1; i < words.length; i++) {
            putAllChars(words[i], graph);
            if (!addEdge(words[i - 1], words[i], graph)) {
                return "";
            }
        }
        StringBuilder sb = new StringBuilder();
        Set<Character> visited = new HashSet<>();
        for (Character c : graph.keySet()) {
            if (!dfs(c, graph, sb, visited, new HashSet<>())) {
                return "";
            }
        }
        return sb.reverse().toString();
    }
    
    private boolean dfs(Character c, Map<Character, Set<Character>> graph, StringBuilder sb, Set<Character> visited, Set<Character> path) {
        if (visited.contains(c)) {
            return true;
        }
        path.add(c);
        for (Character next : graph.getOrDefault(c, new HashSet<>())) {
            if (path.contains(next)) {
                return false;
            }
            if (!dfs(next, graph, sb, visited, path)) {
                return false;
            }
        }
        path.remove(c);
        visited.add(c);
        sb.append(c);
        return true;
    }
    
    private void putAllChars(String word, Map<Character, Set<Character>> graph) {
        for (int i = 0; i < word.length(); i++) {
            graph.putIfAbsent(word.charAt(i), new HashSet<>());
        }
    }
    
    private boolean addEdge(String smaller, String larger, Map<Character, Set<Character>> graph) {
        for (int i = 0; i < Math.min(smaller.length(), larger.length()); i++) {
            char a = smaller.charAt(i);
            char b = larger.charAt(i);
            if (a == b) {
                continue;
            }
            graph.get(a).add(b);
            return true;
        }
        return smaller.length() <= larger.length();
    }
}


Version #2 BFS + indegree 四刷 05/2022
出现了前面出现的bug就是要提前把每个word里面所有的characters都加到map里面
另外一个就是有环的问题,这里BFS就不如DFS直观

Runtime: 10 ms, faster than 19.83% of Java online submissions for Alien Dictionary.
Memory Usage: 43.1 MB, less than 26.43% of Java online submissions for Alien Dictionary.

class Solution {
    public String alienOrder(String[] words) {
        // From "wrt" < "wrf" we got "t" < "f"
        // Then we can construct part of the graph t -> f
        // We can build to whole graph with this rule and do a topological sort
        Map<Character, Set<Character>> map = new HashMap<>();
        Map<Character, Integer> indegree = new HashMap<>();
        try {
            buildGraph(words, map, indegree);
        } catch (IllegalArgumentException e) {
            return "";
        }
       
        Queue<Character> que = new ArrayDeque<>();
        for (Map.Entry<Character, Integer> entry : indegree.entrySet()) {
            if (entry.getValue() == 0) {
                que.offer(entry.getKey());
            }
        }
        StringBuilder sb = new StringBuilder();
        while (!que.isEmpty()) {
            Character curr = que.poll();
            sb.append(curr);
            for (Character next : map.getOrDefault(curr, new HashSet<>())) {
                int nId = indegree.get(next) - 1;
                if (nId == 0) {
                    que.offer(next);
                }
                indegree.put(next, nId);
            }
        }
        if (sb.length() != map.keySet().size()) {
            return "";
        }
        return sb.toString();
    }
    private void buildGraph(String[] words, Map<Character, Set<Character>> map, Map<Character, Integer> indegree) throws IllegalArgumentException {
        String prev = null;
        for (String curr : words) {
            for (char c : curr.toCharArray()) {
                map.putIfAbsent(c, new HashSet<>());
                indegree.putIfAbsent(c, 0);
            }
            if (prev == null) {
                prev = curr;
                continue;
            }
            int i = 0;
            int prevLen = prev.length();
            int currLen = curr.length();
            Character from = null, to = null;
            for (i = 0; i < Math.min(prevLen, currLen); i++) {
                if (prev.charAt(i) != curr.charAt(i)) {
                    from = prev.charAt(i);
                    to = curr.charAt(i);
                    break;
                }
            }
            if (i == Math.min(prevLen, currLen)) {
                if (prevLen > currLen) {
                    throw new IllegalArgumentException();
                }
                continue;
            }
            if (!map.get(from).contains(to)) {
                map.get(from).add(to);
                int id = indegree.getOrDefault(to, 0);
                indegree.put(to, id + 1);
            }
            
            prev = curr;
        }
    }
}


Version #1 DFS
三刷

记住dfs做topological sort的最核心的一点就是
only add nodes whose children are fully explored to stack
依然卡在了忘了把所有出现的char但是不具备决定关系的char加到map里面

2.33 %

class Solution {
    public String alienOrder(String[] words) {
        // dfs topological sort, only add nodes whose children are fully explored to stack
        if (words == null || words.length == 0) return "";
        Map<Character, Set<Character>> map = buildGraph(words);
        Deque<Character> stack = new ArrayDeque<>();
        Set<Character> visited = new HashSet<>();
        for (char c : map.keySet()) {
            if (!visited.contains(c) && !dfs(c, map, stack, visited, new HashSet<>())) { // has cycle
                return "";
            }
        }
        StringBuilder sb = new StringBuilder();
        while (!stack.isEmpty()) {
            sb.append(stack.pop());
        }
        return sb.toString();
    }
   
    private boolean dfs(char c, Map<Character, Set<Character>> map, Deque<Character> stack,
                       Set<Character> visited, Set<Character> path) {
        if (path.contains(c)) { // see a loop
            return false;
        }
        if (visited.contains(c)) {
            return true;
        }
        path.add(c);
        visited.add(c);
        Set<Character> children = map.get(c);
        for (Character next : children) {
            if (!dfs(next, map, stack, visited, path)) {
                return false;
            }
        }
        path.remove(c);
        stack.push(c);
        return true;
    }
    private Map<Character, Set<Character>> buildGraph(String[] words) {
        Map<Character, Set<Character>> map = new HashMap<>();
        for (int i = 1; i < words.length; i++) {
            String prev = words[i - 1];
            String curr = words[i];
            for (int j = 0; j < Math.min(prev.length(), curr.length()); j++) {
                char p = prev.charAt(j);
                char c = curr.charAt(j);
                if (p != c) {
                    map.computeIfAbsent(p, set -> new HashSet<>()).add(c);
                    break;
                }
            }
        }
        for (String word : words) {
            for (int i = 0; i < word.length(); i++) {
                char c = word.charAt(i);
                map.putIfAbsent(c, new HashSet<>());
            }
        }
        return map;
    }
}

二刷
依然是卡在不能决定顺序的所有characters也要输出 这个点上
这一次上来直接就写了hashmap
依然是把words里面所有的char都先加到hashmap 的key里面
73.56 % 

class Solution {
    public String alienOrder(String[] words) {
        if (words == null || words.length == 0) return "";
        Map<Character, List<Character>> graph = buildGraph(words);
        if (graph == null) return ""; //invalid graph
        List<Character> result = new ArrayList<>();
        Set<Character> path = new HashSet<>();
        Set<Character> visited = new HashSet<>();
        for (Character curr : graph.keySet()) {
            if (!dfs(curr, graph, path, visited, result)) return "";
        }
        StringBuilder sb = new StringBuilder();
        for (int i = result.size() - 1; i >= 0; i--) {
            sb.append(result.get(i));
        }
        return sb.toString();
    }

     //if there's a cycle, return false
    private boolean dfs(Character curr, Map<Character, List<Character>> graph, Set<Character> path, Set<Character> visited, List<Character> result) {
   
        if (path.contains(curr)) return false; // there's a cycle
        if (visited.contains(curr)) return true; // the area has already been explored
        List<Character> neighbors = graph.get(curr);
        if (neighbors != null) {
            path.add(curr);
            for (Character neighbor : neighbors) {
                if (!dfs(neighbor, graph, path, visited, result)) {
                    path.remove(curr);
                    return false;
                }
            }
            path.remove(curr);
        }
        // neighbors == null, curr is the last node
        visited.add(curr);
        result.add(curr);
        return true;
    }

    private Map<Character, List<Character>> buildGraph(String[] words) {
        Map<Character, List<Character>> graph = new HashMap<>();
        for (char c : words[0].toCharArray()) {
            graph.put(c, new ArrayList<>());
        }
        for (int i = 0; i < words.length - 1; i++) {
            for (char c : words[i + 1].toCharArray()) {
                if (!graph.containsKey(c)) graph.put(c, new ArrayList<Character>());
            }
            // relation[0] current char, relation[1] next char
            char[] relation = getNeighbor(words[i], words[i + 1]);
            //You may assume that if a is a prefix of b, then a must appear before b in the given dictionary.
            if (relation == null) return null;
            if (relation.length == 0) continue;
       
            graph.get(relation[0]).add(relation[1]);
        }
        return graph;
    }

    private char[] getNeighbor(String curr, String next) {
        int index = 0;
        while (index < curr.length() && index < next.length()) {
            char c1 = curr.charAt(index);
            char c2 = next.charAt(index);
            if (c1 != c2) return new char[]{c1, c2};
            else index++;
        }
        //next is shorter than curr, next is a prefix of curr
        if (index < curr.length()) {
            return null;
        } else { // curr is a prefix of next || curr.equals(next)
            return new char[0];
        }
    }

}

一刷
感觉自己DFS写的不好所以用DFS写一下
对于graph表示的方法有问题
一开始写了一个boolean[26][26]后来发现不符合题意
因为如果key是26默认所有characters都出现了,然而并不是这样,只有看到的character才算数
所以用hashmap比较直观

有一个很大的bug就是在buildGraph的时候要把所有characters加入到hashmap里面
因为当两个string有char不一样的时候就会break,所以直接后面的characters都看不见了

一个比较无脑的方法就是先把所有words都看一下,把所有的char都作为key提前加入到hashmap里面,就不会有遗漏了

对topological sorting的定义还是理解的不到位,觉得如果只有一个输入词的话没有顺序可以输出,然而对于topo sorting来说,不能决定的顺序就是可以以任意顺序输出
所以hashmap需要包含所有的word里面的characters

 55.16 % 
public class Solution {
    public String alienOrder(String[] words) {
        if (words == null) return "";
        Map<Character, Set<Character>> graph = new HashMap<>();
        for (String word : words) {
            for (char ch : word.toCharArray()) {
                if (!graph.containsKey(ch)) graph.put(ch, new HashSet<>());
            }
        }
        if (!buildGraph(words, graph)) return "";
        for (Character c : graph.keySet()) {
        }
        //TODO
        Stack<Character> order = new Stack<>();
        Boolean[] hasOrder = new Boolean[26];
        Set<Character> visiting = new HashSet<>();
        for (Character c : graph.keySet()) {
            if (!getOrder(c, graph, visiting, hasOrder, order)) return "";
        }
        StringBuilder sb = new StringBuilder();
        while (!order.isEmpty()) {
            sb.append(order.pop());
        }
        return sb.toString();
    }
    private boolean getOrder(Character c, Map<Character, Set<Character>> graph, Set<Character> visiting, Boolean[] hasOrder, Stack<Character> order) {
        if (visiting.contains(c)) return false;
        if (hasOrder[c - 'a'] != null) return hasOrder[c - 'a'];
        if (!graph.containsKey(c)) {
            hasOrder[c - 'a'] = true;
            order.push(c);
            return true;
        }
        visiting.add(c);
        boolean currHasOrder = true;
        for (Character neighbor : graph.get(c)) {
            if (!getOrder(neighbor, graph, visiting, hasOrder, order)) {
                currHasOrder = false;
                break;
            }
        }
        visiting.remove(c);
        hasOrder[c - 'a'] = currHasOrder;
        order.push(c);
        return currHasOrder;
 
    }
    private boolean buildGraph(String[] words, Map<Character, Set<Character>> graph) {
        // "aa" must appear before "aac"
        String prev = words[0];
        String curr;
        for (int i = 1; i < words.length; i++) {
            curr = words[i];
            int k;
            for (k = 0; k < Math.min(prev.length(), curr.length()); k++) {
                if (prev.charAt(k) != curr.charAt(k)) {
                    graph.get(prev.charAt(k)).add(curr.charAt(k));
                    break;
                }
            }
            //curr is prefix of prev
            if (prev.length() > curr.length() && k == curr.length()) return false;
            //if (k == prev.length()) -> prev is prefix of curr
            //no need to add any new edge
     
            prev = curr;
        }
        return true;
    }
}




No comments:

Post a Comment