【Lintcode】892. Alien Dictionary

題目地址:

https://www.lintcode.com/problem/alien-dictionary/description

給定一個字符串數組,在某種字典序下這個數組是有序的,要求求這個字典序並以字符串的形式返回。如果不存在這樣的字典序則返回空串。如果存在多個合法解,則返回字典序最小的那個(這裏的字典序指的是英文字母的自然的字典序)。

本質是求拓撲排序。首先要排除幾個特殊情況,如果數組裏只有一個字符串,那麼只需要將其字符排序後返回即可。如果數組中有兩個字符串,後面的那個是前面的那個的子串,但是後面的那個長度更短,這時候也是不存在合法解的,因爲字符串的比較中若同位置的字符都相等,那麼短的應該在前面。排除完特殊情況後,通過字符串的順序建圖,如果某個字符aa順序在另一個bb之前,則在圖中從aabb連一條有向邊。最後對圖做拓撲排序即可。

但是,由於題目要求返回所有拓撲序中字典序最小的那個,這個時候DFS是不管用的,DFS不能對頂點分層。而BFS則可以,BFS做拓撲排序需要算一下每個點的入度,並且要用隊列。這裏需要用優先隊列,將字典序最小的那個入度爲00的點入隊。代碼如下:

import java.util.*;

public class Solution {
    /**
     * @param words: a list of words
     * @return: a string which is correct order
     */
    public String alienOrder(String[] words) {
        // Write your code here
        if (words == null || words.length == 0) {
            return "";
        }
        
        // 嚴格來說,在數組長度爲1的時候要先對其排序後再返回
        if (words.length == 1) {
            char[] res = words[0].toCharArray();
            Arrays.sort(res);
            return new String(res);
        }
        
        // 對words進行建圖
        Map<Character, Set<Character>> graph = buildGraph(words);
        // 如果返回了空圖說明不存在合法字典序,返回空串
        if (graph == null) {
            return "";
        }
        
        // 得到所有頂點的入度
        Map<Character, Integer> indegrees = getIndegrees(graph);
        
        // 開一個優先隊列,將所有入度爲0的頂點入隊
        PriorityQueue<Character> pq = new PriorityQueue<>();
        for (Map.Entry<Character, Integer> entry : indegrees.entrySet()) {
            if (entry.getValue() == 0) {
                pq.offer(entry.getKey());
            }
        }
        
        StringBuilder sb = new StringBuilder();
        while (!pq.isEmpty()) {
            char cur = pq.poll();
            sb.append(cur);
            for (char next : graph.get(cur)) {
                indegrees.put(next, indegrees.get(next) - 1);
                if (indegrees.get(next) == 0) {
                    pq.offer(next);
                }
            }
        }
        // 最後字典序的長度不足字符個數,則說明存在環,返回空串
        if (sb.length() != graph.size()) {
            return "";
        }
        
        return sb.toString();
    }
    
    private Map<Character, Integer> getIndegrees(Map<Character, Set<Character>> graph) {
        Map<Character, Integer> indegrees = new HashMap<>();
        for (Map.Entry<Character, Set<Character>> entry : graph.entrySet()) {
            indegrees.putIfAbsent(entry.getKey(), 0);
            for (char ch : entry.getValue()) {
                indegrees.put(ch, indegrees.getOrDefault(ch, 0) + 1);
            }
        }
        
        return indegrees;
    }
    
    private Map<Character, Set<Character>> buildGraph(String[] words) {
        Map<Character, Set<Character>> graph = new HashMap<>();
        // 把所有字符(也就是頂點)都先加入圖中
        for (int i = 0; i < words.length; i++) {
            for (int j = 0; j < words[i].length(); j++) {
                graph.putIfAbsent(words[i].charAt(j), new HashSet<>());
            }
        }
        
        for (int i = 0; i < words.length - 1; i++) {
            String w1 = words[i], w2 = words[i + 1];
            int idx = 0;
            while (idx < w1.length() && idx < w2.length()) {
                char c1 = w1.charAt(idx), c2 = w2.charAt(idx);
                if (c1 != c2) {
                    graph.get(c1).add(c2);
                    break;
                }
                idx++;
            }
            
            // 這個對應的情況是兩個字符串對應位置字符都相等,但排在後面的字符串更短,
            // 這時是不存在合法字典序的,直接返回空圖
            if (idx == w2.length() && w2.length() < w1.length()) {
                return null;
            }
        }
        
        return graph;
    }
}

時間複雜度O(VlogV+E)O(V\log V+E),空間O(V+E)O(V+E)

算法正確性證明:
算法得出的序列是拓撲排序,這一點是沒問題的。至於爲什麼字典序最小,可以用數學歸納法來證明。當第一個點出隊後,這個點顯然是排在第一位的,之後將其所有鄰居的入度減一,也就是將原圖中的這個點和其所有鄰邊都刪掉,這樣問題規模就變小了,由歸納假設,剩餘圖的字典序最小的拓撲排序就得到了,由數學歸納法知道算法正確。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章