摘要:拓?fù)渑判驈?fù)雜度時(shí)間空間思路首先簡單介紹一下拓?fù)渑判颍@是一個(gè)能夠找出有向無環(huán)圖順序的一個(gè)方法假設(shè)我們有條邊,先將每個(gè)節(jié)點(diǎn)的計(jì)數(shù)器初始化為。最后,我們開始拓?fù)渑判?,從?jì)數(shù)器為的字母開始廣度優(yōu)先搜索。
Alien Dictionary
拓?fù)渑判?/b> 復(fù)雜度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.
時(shí)間 O(N) 空間 O(N)
思路首先簡單介紹一下拓?fù)渑判?,這是一個(gè)能夠找出有向無環(huán)圖順序的一個(gè)方法
假設(shè)我們有3條邊:A->C, B->C, C->D,先將每個(gè)節(jié)點(diǎn)的計(jì)數(shù)器初始化為0。然后我們對(duì)遍歷邊時(shí),每遇到一個(gè)邊,把目的節(jié)點(diǎn)的計(jì)數(shù)器都加1。然后,我們再遍歷一遍,找出所有計(jì)數(shù)器值還是0的節(jié)點(diǎn),這些節(jié)點(diǎn)就是有向無環(huán)圖的“根”。然后我們從根開始廣度優(yōu)先搜索。具體來說,搜索到某個(gè)節(jié)點(diǎn)時(shí),將該節(jié)點(diǎn)加入結(jié)果中,然后所有被該節(jié)點(diǎn)指向的節(jié)點(diǎn)的計(jì)數(shù)器減1,在減1之后,如果某個(gè)被指向節(jié)點(diǎn)的計(jì)數(shù)器變成0了,那這個(gè)被指向的節(jié)點(diǎn)就是該節(jié)點(diǎn)下輪搜索的子節(jié)點(diǎn)。在實(shí)現(xiàn)的角度來看,我們可以用一個(gè)隊(duì)列,這樣每次從隊(duì)列頭拿出來一個(gè)加入結(jié)果中,同時(shí)把被這個(gè)節(jié)點(diǎn)指向的節(jié)點(diǎn)中計(jì)數(shù)器值減到0的節(jié)點(diǎn)也都加入隊(duì)列尾中。需要注意的是,如果圖是有環(huán)的,則計(jì)數(shù)器會(huì)產(chǎn)生斷層,即某個(gè)節(jié)點(diǎn)的計(jì)數(shù)器永遠(yuǎn)無法清零(有環(huán)意味著有的節(jié)點(diǎn)被多加了1,然而遍歷的時(shí)候一次只減一個(gè)1,所以導(dǎo)致無法歸零),這樣該節(jié)點(diǎn)也無法加入到結(jié)果中。所以我們只要判斷這個(gè)結(jié)果的節(jié)點(diǎn)數(shù)和實(shí)際圖中節(jié)點(diǎn)數(shù)相等,就代表無環(huán),不相等,則代表有環(huán)。
對(duì)于這題來說,我們首先要初始化所有節(jié)點(diǎn)(即字母),一個(gè)是該字母指向的字母的集合(被指向的字母在字母表中處于較后的位置),一個(gè)是該字母的計(jì)數(shù)器。然后我們根據(jù)字典開始建圖,但是字典中并沒有顯示給出邊的情況,如何根據(jù)字典建圖呢?其實(shí)邊都暗藏在相鄰兩個(gè)詞之間,比如abc和abd,我們比較兩個(gè)詞的每一位,直到第一個(gè)不一樣的字母c和d,因?yàn)?b>abd這個(gè)詞在后面,所以d在字母表中應(yīng)該是在c的后面。所以每兩個(gè)相鄰的詞都能蘊(yùn)含一條邊的信息。在建圖的同時(shí),實(shí)際上我們也可以計(jì)數(shù)了,對(duì)于每條邊,將較后的字母的計(jì)數(shù)器加1。計(jì)數(shù)時(shí)需要注意的是,我們不能將同樣一條邊計(jì)數(shù)兩次,所以要用一個(gè)集合來排除已經(jīng)計(jì)數(shù)過的邊。最后,我們開始拓?fù)渑判颍瑥挠?jì)數(shù)器為0的字母開始廣度優(yōu)先搜索。為了找到這些計(jì)數(shù)器為0的字母,我們還需要先遍歷一遍所有的計(jì)數(shù)器。
最后,根據(jù)結(jié)果的字母個(gè)數(shù)和圖中所有字母的個(gè)數(shù),判斷時(shí)候有環(huán)即可。無環(huán)直接返回結(jié)果。
注意因?yàn)檫@題代碼很冗長,面試的時(shí)候最好都把幾個(gè)大步驟都寫成子函數(shù),先完成主函數(shù),再實(shí)現(xiàn)各個(gè)子函數(shù),比如初始化圖,建圖,加邊,排序,都可以分開
要先對(duì)字典里所有存在的字母初始化入度為0,否則之后建圖可能會(huì)漏掉一些沒有入度的字母
"a"+"b"+""和"a"+""+"b"是不一樣的,前者先算數(shù)字和,后者則是字符串拼接
因?yàn)樽值淅镉兄貜?fù)的邊,所有要先判斷,已經(jīng)添加過的邊不要重復(fù)添加
代碼public class Solution { public String alienOrder(String[] words) { // 節(jié)點(diǎn)構(gòu)成的圖 Map> graph = new HashMap >(); // 節(jié)點(diǎn)的計(jì)數(shù)器 Map indegree = new HashMap (); // 結(jié)果存在這個(gè)里面 StringBuilder order = new StringBuilder(); // 初始化圖和計(jì)數(shù)器 initialize(words, graph, indegree); // 建圖并計(jì)數(shù) buildGraphAndGetIndegree(words, graph, indegree); // 拓?fù)渑判虻淖詈笠徊?,根?jù)計(jì)數(shù)器值廣度優(yōu)先搜索 topologicalSort(order, graph, indegree); // 如果大小相等說明無環(huán) 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); // 對(duì)每個(gè)單詞的每個(gè)字母初始化計(jì)數(shù)器和圖節(jié)點(diǎn) 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++){ // 每兩個(gè)相鄰的詞進(jìn)行比較 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); // 如果相同則繼續(xù),找到兩個(gè)單詞第一個(gè)不相同的字母 if(from == to) continue; // 如果這兩個(gè)字母構(gòu)成的邊還沒有使用過,則 if(!edges.contains(from+""+to)){ Set set = graph.get(from); set.add(to); // 將后面的字母加入前面字母的Set中 graph.put(from, set); Integer toin = indegree.get(to); toin++; // 更新后面字母的計(jì)數(shù)器,+1 indegree.put(to, toin); // 記錄這條邊已經(jīng)處理過了 edges.add(from+""+to); break; } } } } private void topologicalSort(StringBuilder order, Map > graph, Map indegree){ // 廣度優(yōu)先搜索的隊(duì)列 Queue queue = new LinkedList (); // 將有向圖的根,即計(jì)數(shù)器為0的節(jié)點(diǎn)加入隊(duì)列中 for(Character key : indegree.keySet()){ if(indegree.get(key) == 0){ queue.offer(key); } } // 搜索 while(!queue.isEmpty()){ Character curr = queue.poll(); // 將隊(duì)頭節(jié)點(diǎn)加入結(jié)果中 order.append(curr); Set set = graph.get(curr); if(set != null){ // 對(duì)所有該節(jié)點(diǎn)指向的節(jié)點(diǎn),更新其計(jì)數(shù)器,-1 for(Character c : set){ Integer val = indegree.get(c); val--; // 如果計(jì)數(shù)器歸零,則加入隊(duì)列中待處理 if(val == 0){ queue.offer(c); } indegree.put(c, val); } } } } }
新建一個(gè)AlienChar數(shù)據(jù)結(jié)構(gòu)重寫,只用一個(gè)Map作為Graph自身
public class Solution { public String alienOrder(String[] words) { Mapgraph = new HashMap (); // 如果建圖失敗,比如有a->b和b->a這樣的邊,就返回false boolean isBuildSucceed = buildGraph(words, graph); if(!isBuildSucceed){ return ""; } // 在建好的圖中根據(jù)拓?fù)渑判虮闅v String order = findOrder(graph); return order.length() == graph.size() ? order : ""; } private boolean buildGraph(String[] words, Map graph){ HashSet visited = new HashSet (); // 初始化圖,每個(gè)字母都初始化入度為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; // 找到相鄰兩個(gè)單詞第一個(gè)不一樣的字母 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; } } // 如果有環(huán),則建圖失敗 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 "" }
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/64606.html
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...
摘要:題目鏈接題目分析給定一個(gè)單詞數(shù)組和一個(gè)字符串,判斷給定的數(shù)組是否滿足給定字符串的順序。思路按給定字符串,替換成正常順序的單詞。再判斷之前和之后的數(shù)組是否相同。最終代碼若覺得本文章對(duì)你有用,歡迎用愛發(fā)電資助。 D61 953. Verifying an Alien Dictionary 題目鏈接 953. Verifying an Alien Dictionary 題目分析 給定一個(gè)單詞...
摘要:題目鏈接圖用,和題類似。要找到所有字母的,之后用存下入度為的字母,然后輸出。要記錄下每個(gè)字母對(duì)應(yīng)的所有字母,防止重復(fù)。求的過程可以用,從所有單詞的開始,對(duì)不同的字母存入度,相同的去下一層。 Alien Dictionary 題目鏈接:https://leetcode.com/problems... 圖用topological sort,和course schedule題類似。要找到所有...
摘要:本章主要介紹字典的概念,基本操作以及一些進(jìn)階操作。使用字典在中,字典是一系列鍵值對(duì)。中用花括號(hào)來表示字典。代碼定義空字典的語法結(jié)果如果要修改字典中的值,只需通過鍵名訪問就行。 《Python編程:從入門到實(shí)踐》筆記。本章主要介紹字典的概念,基本操作以及一些進(jìn)階操作。 1. 使用字典(Dict) 在Python中,字典是一系列鍵值對(duì)。每個(gè)鍵都與一個(gè)值相關(guān)聯(lián),用鍵來訪問值。Python中用...
摘要:所以只要驗(yàn)證滿足這個(gè)條件,我們則可以確定這個(gè)較長的字符串也是可分解的。同時(shí),我們用數(shù)組記錄下字符串長度遞增時(shí)可分解的情況,以供之后使用,避免重復(fù)計(jì)算。當(dāng)遍歷完這個(gè)詞典并找出所有以第一個(gè)字母開頭的詞以后,我們進(jìn)入下一輪搜索。 Word Break I Given a string s and a dictionary of words dict, determine if s can ...
閱讀 2638·2021-11-18 10:02
閱讀 2289·2021-09-30 09:47
閱讀 1808·2021-09-27 14:01
閱讀 3120·2021-08-16 11:00
閱讀 3173·2019-08-30 11:06
閱讀 2404·2019-08-29 17:29
閱讀 1543·2019-08-29 13:19
閱讀 453·2019-08-26 13:54