題目:
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, wherewords 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.
鏈接: http://leetcode.com/problems/alien-dictionary/
題解:
這道題也是卡了很久,原因在於題意理解不明確。方法還是Topological Sorting,這題的意思是 - Words are sorted, 跟每個Word里面的letter順序並沒有什么關系。就比如"apple"排在"banana"前面,但並不是"apple"里面的"a"就排在"p"前面。所以之前花了不少時間根據每個單詞內部的順序構造edges,結果都是錯的。 這種東西歸根結底還是我自己的英語不好。寫了一大堆總是通不過,發現題目真正含義以后就好很多了,以后面試的話也要多交流多溝通,免得題意弄不清楚白浪費時間做題。另外,對於圖來說,圖的三種表達方式, list of edges, ajacency matrix和ajacency lists一定要搞清楚,多弄明白。
Kahn's Algorithm: 先把輸入words轉化為Adjacency Lists或者list of edges,然后使用Course Schedule II的方法進行Topological Sorting。這里我們使用Kahn's Algorithm,先計算inDegree,然后維護一個Queue來進行BFS。
Time Complexity - O(VE),Space Complexity - O(VE)
public class Solution { public String alienOrder(String[] words) { // Topological sorting - Kahn's Algorithm if(words == null || words.length == 0) { return ""; } Map<Character, HashSet<Character>> map = new HashMap<>(); Map<Character, Integer> inDegree = new HashMap<>(); for(String s : words) { for(int i = 0; i < s.length(); i++) { char c = s.charAt(i); inDegree.put(c, 0); } } for(int i = 1; i < words.length; i++) { // find (prevChar, curChar) pairs as edges String prevStr = words[i - 1]; String curStr = words[i]; int len = Math.min(prevStr.length(), curStr.length()); for(int j = 0; j < len; j++) { char curChar = curStr.charAt(j); char prevChar = prevStr.charAt(j); if(curChar == prevChar) { continue; } else { // find edges; if(map.containsKey(prevChar)) { if(!map.get(prevChar).contains(curChar)) { map.get(prevChar).add(curChar); inDegree.put(curChar, inDegree.get(curChar) + 1); } } else { HashSet<Character> tmpSet = new HashSet<>(); tmpSet.add(curChar); map.put(prevChar, tmpSet); inDegree.put(curChar, inDegree.get(curChar) + 1); } break; } } } Queue<Character> queue = new LinkedList<>(); for(char c : inDegree.keySet()) { if(inDegree.get(c) == 0) { queue.offer(c); } } StringBuilder res = new StringBuilder(); while(!queue.isEmpty()) { char c = queue.poll(); res.append(c); if(map.containsKey(c)) { for(char l : map.get(c)) { inDegree.put(l, inDegree.get(l) - 1); if(inDegree.get(l) == 0) { queue.offer(l); } } } } if(res.length() != inDegree.size()) { return ""; } return res.toString(); } }
題外話:
這幾天食物過於豐富。周五BCD豆腐,周六劉一手火鍋(改名了),周日又去了法拉盛吃老周全羊館,結果就懈怠了。不過和小伙伴們玩玩三國殺放松一下也是很好的。房子的裝修也接近尾聲,明天周一估計就可以收工了,希望一切順利。 同時,明天也開始為期一周的培訓,主要是Modern Web Development,講JavaScript的。同時Coursera也有一門講AngularJS的課程,香港科技大學開設的,明天開始第一天。要趁這個機會好好學一些AngularJS。
二刷:
簡化了一下。思路不那么亂了。有幾個特殊的點要注意:
- 輸入中只含有26個小寫字母,所以我們可以可以用一個int[26]來計算inDegree
- 輸出的時候只輸出在words里出現過的字符,所以先把字符保存在一個set里。 其實這里也可以和map一起遍歷,但需要不少額外的邊界條件
- 注意單詞內部的順序沒有關系,只有單詞和前一個單詞相同位置不同字間才有先后順序。
- 要注意圖的adjancency lists表示結構, 使用Map<Character, Set<Character>>可能會比 Map<Character, List<Character>>快。同時要注意這道題里key保存的是先輸出的字符,value里面保存的是后輸出的字符。inDegree里也是后輸出字符的value被增加
- 結尾的時候假如sb.length() < set.size(),說明無法組成一個topological order,我們返回"",否則返回一個topological sorted string sb.toString();
Java:
public class Solution { public String alienOrder(String[] words) { // Topological sorting - Kahn's Algorithm if(words == null || words.length == 0) { return ""; } Map<Character, Set<Character>> graph = new HashMap<>(); Set<Character> set = new HashSet<>(); for (String word : words) { for (int i = 0; i < word.length(); i++) { set.add(word.charAt(i)); } } int[] inDegree = new int[26]; for (int k = 1; k < words.length; k++) { String preStr = words[k - 1]; String curStr = words[k]; for (int i = 0; i < Math.min(preStr.length(), curStr.length()); i++) { char preChar = preStr.charAt(i); char curChar = curStr.charAt(i); if (preChar != curChar) { if (!graph.containsKey(preChar)) { graph.put(preChar, new HashSet<Character>()); } if (!graph.get(preChar).contains(curChar)) { inDegree[curChar - 'a']++; } graph.get(preChar).add(curChar); break; } } } Queue<Character> queue = new LinkedList<>(); for (int i = 0; i < inDegree.length; i++) { if (inDegree[i] == 0) { char c = (char)('a' + i); if (set.contains(c)) { queue.offer(c); } } } StringBuilder sb = new StringBuilder(); while (!queue.isEmpty()) { char c = queue.poll(); sb.append(c); if (graph.containsKey(c)) { for (char l : graph.get(c)) { inDegree[l - 'a']--; if (inDegree[l - 'a'] == 0) { queue.offer(l); } } } } return sb.length() != set.size() ? "" : sb.toString(); } }
Reference:
https://leetcode.com/discuss/53997/the-description-is-wrong
https://leetcode.com/discuss/71991/8ms-clean-java-using-topological-sort-and-dfs
https://leetcode.com/discuss/65274/java-solution-basic-topological-sort
https://leetcode.com/discuss/69894/fastest-java-solution-topological-comments-improvements
https://leetcode.com/discuss/54002/simple-idea-based-on-dfs-4ms-in-c
https://leetcode.com/discuss/54549/java-toposort-solution-clean
https://leetcode.com/discuss/54188/16-18-lines-python-29-lines-c