leetcode阶段总结——拓扑排序
leetcode階段總結(jié)——拓?fù)渑判?/strong>
?
leetcode中經(jīng)常出現(xiàn)的題型之一。其中,拓?fù)渑判虻母拍羁梢詤⒖歼@里,這里主要總結(jié)一下前300題中出現(xiàn)的幾個(gè)關(guān)于拓?fù)渑判虻念},以待之后復(fù)習(xí)的時(shí)候查找。
leetcode207 課程表
??? 現(xiàn)在你總共有 n 門課需要選,記為 0 到 n-1。
??? 在選修某些課程之前需要一些先修課程。 例如,想要學(xué)習(xí)課程 0 ,你需要先完成課程 1 ,我們用一個(gè)匹配來表示他們: [0,1]
??? 給定課程總量以及它們的先決條件,判斷是否可能完成所有課程的學(xué)習(xí)?
??? 示例 1:
??????? 輸入: 2, [[1,0]]
??????? 輸出: true
??????? 解釋: 總共有 2 門課程。學(xué)習(xí)課程 1 之前,你需要完成課程 0。所以這是可能的。
??? 示例 2:
??????? 輸入: 2, [[1,0],[0,1]]
??????? 輸出: false
??????? 解釋: 總共有 2 門課程。學(xué)習(xí)課程 1 之前,你需要先完成?課程 0;并且學(xué)習(xí)課程 0 之前,你還應(yīng)先完成課程 1。這是不可能的。
??? 說明:
??????? 輸入的先決條件是由邊緣列表表示的圖形,而不是鄰接矩陣。詳情請(qǐng)參見圖的表示法。
??????? 你可以假定輸入的先決條件中沒有重復(fù)的邊。
??? 提示:
??????? 這個(gè)問題相當(dāng)于查找一個(gè)循環(huán)是否存在于有向圖中。如果存在循環(huán),則不存在拓?fù)渑判?#xff0c;因此不可能選取所有課程進(jìn)行學(xué)習(xí)。
??????? 通過 DFS 進(jìn)行拓?fù)渑判?- 一個(gè)關(guān)于Coursera的精彩視頻教程(21分鐘),介紹拓?fù)渑判虻幕靖拍睢?br /> ??????? ?
??????
本題是一道經(jīng)典的「拓?fù)渑判颉箚栴}。
給定一個(gè)包含 nnn 個(gè)節(jié)點(diǎn)的有向圖 GGG,我們給出它的節(jié)點(diǎn)編號(hào)的一種排列,如果滿足:
??? 對(duì)于圖 GGG 中的任意一條有向邊 (u,v)(u, v)(u,v),uuu 在排列中都出現(xiàn)在 vvv 的前面。
那么稱該排列是圖 GGG 的「拓?fù)渑判颉?。根?jù)上述的定義,我們可以得出兩個(gè)結(jié)論:
??? 如果圖 GGG 中存在環(huán)(即圖 GGG 不是「有向無環(huán)圖」),那么圖 GGG 不存在拓?fù)渑判?。這是因?yàn)榧僭O(shè)圖中存在環(huán) x1,x2,? ,xn,x1x_1, x_2, \cdots, x_n, x_1x1?,x2?,?,xn?,x1?,那么 x1x_1x1? 在排列中必須出現(xiàn)在 xnx_nxn? 的前面,但 xnx_nxn? 同時(shí)也必須出現(xiàn)在 x1x_1x1? 的前面,因此不存在一個(gè)滿足要求的排列,也就不存在拓?fù)渑判?#xff1b;
??? 如果圖 GGG 是有向無環(huán)圖,那么它的拓?fù)渑判蚩赡懿恢挂环N。舉一個(gè)最極端的例子,如果圖 GGG 值包含 nnn 個(gè)節(jié)點(diǎn)卻沒有任何邊,那么任意一種編號(hào)的排列都可以作為拓?fù)渑判颉?/p>
有了上述的簡(jiǎn)單分析,我們就可以將本題建模成一個(gè)求拓?fù)渑判虻膯栴}了:
??? 我們將每一門課看成一個(gè)節(jié)點(diǎn);
??? 如果想要學(xué)習(xí)課程 AAA 之前必須完成課程 BBB,那么我們從 BBB 到 AAA 連接一條有向邊。這樣以來,在拓?fù)渑判蛑?#xff0c;BBB 一定出現(xiàn)在 AAA 的前面。
求出該圖是否存在拓?fù)渑判?#xff0c;就可以判斷是否有一種符合要求的課程學(xué)習(xí)順序。事實(shí)上,由于求出一種拓?fù)渑判蚍椒ǖ淖顑?yōu)時(shí)間復(fù)雜度為 O(n+m)O(n+m)O(n+m),其中 nnn 和 mmm 分別是有向圖 GGG 的節(jié)點(diǎn)數(shù)和邊數(shù),方法見 210. 課程表 II 的官方題解。而判斷圖 GGG 是否存在拓?fù)渑判?#xff0c;至少也要對(duì)其進(jìn)行一次完整的遍歷,時(shí)間復(fù)雜度也為 O(n+m)O(n+m)O(n+m)。因此不可能存在一種僅判斷圖是否存在拓?fù)渑判虻姆椒?#xff0c;它的時(shí)間復(fù)雜度在漸進(jìn)意義上嚴(yán)格優(yōu)于 O(n+m)O(n+m)O(n+m)。這樣一來,我們使用和 210. 課程表 II 完全相同的方法,但無需使用數(shù)據(jù)結(jié)構(gòu)記錄實(shí)際的拓?fù)渑判颉榱藬⑹龅耐暾?#xff0c;下面的兩種方法與 210. 課程表 II 的官方題解 完全相同,但在「算法」部分后的「優(yōu)化」部分說明了如何省去對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu)。
方法一:深度優(yōu)先搜索
思路
我們可以將深度優(yōu)先搜索的流程與拓?fù)渑判虻那蠼饴?lián)系起來,用一個(gè)棧來存儲(chǔ)所有已經(jīng)搜索完成的節(jié)點(diǎn)。
??? 對(duì)于一個(gè)節(jié)點(diǎn) uuu,如果它的所有相鄰節(jié)點(diǎn)都已經(jīng)搜索完成,那么在搜索回溯到 uuu 的時(shí)候,uuu 本身也會(huì)變成一個(gè)已經(jīng)搜索完成的節(jié)點(diǎn)。這里的「相鄰節(jié)點(diǎn)」指的是從 uuu 出發(fā)通過一條有向邊可以到達(dá)的所有節(jié)點(diǎn)。
假設(shè)我們當(dāng)前搜索到了節(jié)點(diǎn) uuu,如果它的所有相鄰節(jié)點(diǎn)都已經(jīng)搜索完成,那么這些節(jié)點(diǎn)都已經(jīng)在棧中了,此時(shí)我們就可以把 uuu 入棧??梢园l(fā)現(xiàn),如果我們從棧頂往棧底的順序看,由于 uuu 處于棧頂?shù)奈恢?#xff0c;那么 uuu 出現(xiàn)在所有 uuu 的相鄰節(jié)點(diǎn)的前面。因此對(duì)于 uuu 這個(gè)節(jié)點(diǎn)而言,它是滿足拓?fù)渑判虻囊蟮摹?/p>
這樣以來,我們對(duì)圖進(jìn)行一遍深度優(yōu)先搜索。當(dāng)每個(gè)節(jié)點(diǎn)進(jìn)行回溯的時(shí)候,我們把該節(jié)點(diǎn)放入棧中。最終從棧頂?shù)綏5椎男蛄芯褪且环N拓?fù)渑判颉?/p>
算法
對(duì)于圖中的任意一個(gè)節(jié)點(diǎn),它在搜索的過程中有三種狀態(tài),即:
??? 「未搜索」:我們還沒有搜索到這個(gè)節(jié)點(diǎn);
??? 「搜索中」:我們搜索過這個(gè)節(jié)點(diǎn),但還沒有回溯到該節(jié)點(diǎn),即該節(jié)點(diǎn)還沒有入棧,還有相鄰的節(jié)點(diǎn)沒有搜索完成);
??? 「已完成」:我們搜索過并且回溯過這個(gè)節(jié)點(diǎn),即該節(jié)點(diǎn)已經(jīng)入棧,并且所有該節(jié)點(diǎn)的相鄰節(jié)點(diǎn)都出現(xiàn)在棧的更底部的位置,滿足拓?fù)渑判虻囊蟆?/p>
通過上述的三種狀態(tài),我們就可以給出使用深度優(yōu)先搜索得到拓?fù)渑判虻乃惴鞒?#xff0c;在每一輪的搜索搜索開始時(shí),我們?nèi)稳∫粋€(gè)「未搜索」的節(jié)點(diǎn)開始進(jìn)行深度優(yōu)先搜索。
??? 我們將當(dāng)前搜索的節(jié)點(diǎn) uuu 標(biāo)記為「搜索中」,遍歷該節(jié)點(diǎn)的每一個(gè)相鄰節(jié)點(diǎn) vvv:
??????? 如果 vvv 為「未搜索」,那么我們開始搜索 vvv,待搜索完成回溯到 uuu;
??????? 如果 vvv 為「搜索中」,那么我們就找到了圖中的一個(gè)環(huán),因此是不存在拓?fù)渑判虻?#xff1b;
??????? 如果 vvv 為「已完成」,那么說明 vvv 已經(jīng)在棧中了,而 uuu 還不在棧中,因此 uuu 無論何時(shí)入棧都不會(huì)影響到 (u,v)(u, v)(u,v) 之前的拓?fù)潢P(guān)系,以及不用進(jìn)行任何操作。
??? 當(dāng) uuu 的所有相鄰節(jié)點(diǎn)都為「已完成」時(shí),我們將 uuu 放入棧中,并將其標(biāo)記為「已完成」。
在整個(gè)深度優(yōu)先搜索的過程結(jié)束后,如果我們沒有找到圖中的環(huán),那么棧中存儲(chǔ)這所有的 nnn 個(gè)節(jié)點(diǎn),從棧頂?shù)綏5椎捻樞蚣礊橐环N拓?fù)渑判颉?/p> class Solution {static Map<Integer,List<Integer>> map;static int [] state;public boolean canFinish(int numCourses, int[][] p) {if(p==null){return true;}map=new HashMap<Integer,List<Integer>>();state=new int[numCourses];for(int i=0;i<p.length;i++){if(map.containsKey(p[i][0])){map.get(p[i][0]).add(p[i][1]);}else{List<Integer> list=new ArrayList<Integer>();list.add(p[i][1]);map.put(p[i][0], list);}}for(int i=0;i<p.length;i++){if(state[p[i][0]]==2){continue;}if(!dfs(p[i][0])){return false;}}return true;} public static boolean dfs(Integer i){if(state[i]==2) return true;if(state[i]==1) return false;state[i]=1;if(map.containsKey(i)){List<Integer> list=map.get(i);for(int j:list){if(!dfs(j)){return false;}}}state[i]=2;return true;} }
?
方法二入度表法:
???????
題意解釋
??? 一共有 n 門課要上,編號(hào)為 0 ~ n-1。
??? 先決條件[1, 0],意思是必須先上課 0,才能上課 1。
??? 給你 n 、和一個(gè)先決條件表,請(qǐng)你判斷能否完成所有課程。
再舉個(gè)生活的例子
??? 先穿內(nèi)褲再穿褲子,先穿打底再穿外套,先穿衣服再戴帽子,是約定俗成的。
??? 內(nèi)褲外穿、光著身子戴帽子等,都會(huì)有點(diǎn)奇怪。
??? 我們遵循穿衣的一條條先后規(guī)則,用一串順序行為,把衣服一件件穿上。
??? 我們遵循課程之間的先后規(guī)則,找到一種上課順序,把所有課一節(jié)節(jié)上完。
??? 示例:n = 6,先決條件表:[[3, 0], [3, 1], [4, 1], [4, 2], [5, 3], [5, 4]]
??? 課 0, 1, 2 沒有先修課,可以直接選。其余的課,都有兩門先修課。
??? 我們用有向圖來展現(xiàn)這種依賴關(guān)系(做事情的先后關(guān)系):
???
??? 這種叫 有向無環(huán)圖,把一個(gè) 有向無環(huán)圖 轉(zhuǎn)成 線性的排序 就叫 拓?fù)渑判颉?br /> ??? 有向圖有 入度 和 出度 的概念:
??????? 如果存在一條有向邊 A --> B,則這條邊給 A 增加了 1 個(gè)出度,給 B 增加了 1 個(gè)入度。
??? 所以,頂點(diǎn) 0、1、2 的入度為 0。頂點(diǎn) 3、4、5 的入度為 2。
每次只能選你能上的課
??? 每次只能選入度為 0 的課,因?yàn)樗灰蕾噭e的課,是當(dāng)下你能上的課。
??? 假設(shè)選了 0,課 3 的先修課少了一門,入度由 2 變 1。
??? 接著選 1,導(dǎo)致課 3 的入度變 0,課 4 的入度由 2 變 1。
??? 接著選 2,導(dǎo)致課 4 的入度變 0。
??? 現(xiàn)在,課 3 和課 4 的入度為 0。繼續(xù)選入度為 0 的課……直到選不到入度為 0 的課。
這很像 BFS
??? 讓入度為 0 的課入列,它們是能直接選的課。
??? 然后逐個(gè)出列,出列代表著課被選,需要減小相關(guān)課的入度。
??? 如果相關(guān)課的入度新變?yōu)?0,安排它入列、再出列……直到?jīng)]有入度為 0 的課可入列。
BFS 前的準(zhǔn)備工作
??? 每門課的入度需要被記錄,我們關(guān)心入度值的變化。
??? 課程之間的依賴關(guān)系也要被記錄,我們關(guān)心選當(dāng)前課會(huì)減小哪些課的入度。
??? 因此我們需要選擇合適的數(shù)據(jù)結(jié)構(gòu),去存這些數(shù)據(jù):
??? 入度數(shù)組:課號(hào) 0 到 n - 1 作為索引,通過遍歷先決條件表求出對(duì)應(yīng)的初始入度。
??? 鄰接表:用哈希表記錄依賴關(guān)系(也可以用二維矩陣,但有點(diǎn)大)
??????? key:課號(hào)
??????? value:依賴這門課的后續(xù)課(數(shù)組)
怎么判斷能否修完所有課?
??? BFS 結(jié)束時(shí),如果仍有課的入度不為 0,無法被選,完成不了所有課。否則,能找到一種順序把所有課上完。
??? 或者:用一個(gè)變量 count 記錄入列的頂點(diǎn)個(gè)數(shù),最后判斷 count 是否等于總課程數(shù)。
代碼
public boolean canFinish(int numCourses, int[][]p ){if(p==null){return true;}int result=0; //記錄節(jié)點(diǎn)被哪些節(jié)點(diǎn)所依賴Map<Integer,List<Integer>> map=new HashMap<Integer,List<Integer>>();int [] indegree=new int[numCourses];for(int i=0;i<p.length;i++){if(map.containsKey(p[i][1])){map.get(p[i][1]).add(p[i][0]);}else{List<Integer> list=new ArrayList<Integer>();list.add(p[i][0]);map.put(p[i][1], list);}indegree[p[i][0]]++;}Queue<Integer> stack=new LinkedList<Integer>();for(int i=0;i<numCourses;i++){if(indegree[i]==0){stack.offer(i);}}while(!stack.isEmpty()){int numZero=stack.poll();result++;if(map.containsKey(numZero)){List<Integer> list=map.get(numZero);for(int num:list){indegree[num]--;if(indegree[num]==0){stack.offer(num);}}}}return result==numCourses;}?
leetcode210 課程表2
??? 現(xiàn)在你總共有 n 門課需要選,記為 0 到 n-1。
??? 在選修某些課程之前需要一些先修課程。 例如,想要學(xué)習(xí)課程 0 ,你需要先完成課程 1 ,我們用一個(gè)匹配來表示他們: [0,1]
??? 給定課程總量以及它們的先決條件,返回你為了學(xué)完所有課程所安排的學(xué)習(xí)順序。
??? 可能會(huì)有多個(gè)正確的順序,你只要返回一種就可以了。如果不可能完成所有課程,返回一個(gè)空數(shù)組。
??? 示例 1:
??????? 輸入: 2, [[1,0]]
??????? 輸出: [0,1]
??????? 解釋: 總共有 2 門課程。要學(xué)習(xí)課程 1,你需要先完成課程 0。因此,正確的課程順序?yàn)?[0,1] 。
??? 示例 2:
??????? 輸入: 4, [[1,0],[2,0],[3,1],[3,2]]
??????? 輸出: [0,1,2,3] or [0,2,1,3]
??????? 解釋: 總共有 4 門課程。要學(xué)習(xí)課程 3,你應(yīng)該先完成課程 1 和課程 2。并且課程 1 和課程 2 都應(yīng)該排在課程 0 之后。
???????????? 因此,一個(gè)正確的課程順序是 [0,1,2,3] 。另一個(gè)正確的排序是 [0,2,1,3] 。
??? 說明:
??????? 輸入的先決條件是由邊緣列表表示的圖形,而不是鄰接矩陣。詳情請(qǐng)參見圖的表示法。
??????? 你可以假定輸入的先決條件中沒有重復(fù)的邊。
??? 提示:
??????? 這個(gè)問題相當(dāng)于查找一個(gè)循環(huán)是否存在于有向圖中。如果存在循環(huán),則不存在拓?fù)渑判?#xff0c;因此不可能選取所有課程進(jìn)行學(xué)習(xí)。
??????? 通過 DFS 進(jìn)行拓?fù)渑判?- 一個(gè)關(guān)于Coursera的精彩視頻教程(21分鐘),介紹拓?fù)渑判虻幕靖拍睢?br /> ??????? ?
??????? 拓?fù)渑判蛞部梢酝ㄟ^ BFS 完成。
如果我們要返回的不僅僅是排序的可能性,而是排序結(jié)果,應(yīng)該怎么辦?其實(shí)很容易想通,在入度表元素變?yōu)榱?某個(gè)元素的記憶化遞歸完成的時(shí)候,就說明這個(gè)元素已經(jīng)“無牽無掛”,沒有前向節(jié)點(diǎn)或前向節(jié)點(diǎn)已經(jīng)加入排序列表中,可以將這個(gè)元素加入列表中了。如果忘記了思路或是看不懂了,更詳細(xì)的解析也可以看這里,寫的非常清楚。
代碼和之前的基本相同。
入度表法
??? class Solution:
??????? def findOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]:
??????????? ##迭代方法
??????????? adjacent = [[] for i in range(numCourses)]
??????????? indegree = [0] * numCourses
??????????? result = []
??????????? for cur,pre in prerequisites:
??????????????? indegree[cur] += 1
??????????????? adjacent[pre].append(cur)
??????????? from collections import deque
??????????? queue = deque()
??????????? for i in range(numCourses):
??????????????? if not indegree[i]:
??????????????????? queue.append(i)
??????????????????? result.append(i)
??????????? while queue:
??????????????? #element = queue.popleft()
??????????????? element = queue.pop()
??????????????? numCourses -= 1
??????????????? for neighbor in adjacent[element]:
??????????????????? indegree[neighbor] -= 1
??????????????????? if not indegree[neighbor]:
??????????????????????? queue.append(neighbor)
??????????????????????? result.append(neighbor)
??????????? #print(result)
??????????? return result if numCourses == 0 else []
遞歸法
??? class Solution:
??????? def findOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]:
??????????? def dfs(i):
??????????????? if flag[i] == -1:
??????????????????? return True
??????????????? if flag[i] == 1:
??????????????????? return False
??????????????? flag[i] = 1
??????????????? for neighbor in adjacent[i]:
??????????????????? if not dfs(neighbor):
??????????????????????? return False
??????????????? flag[i] = -1
??????????????? result.append(i)
??????????????? return True
??? ?
??????????? adjacent = [[] for _ in range(numCourses)]
??????????? flag = [0] * numCourses
??????????? result = []
??????????? for cur,pre in prerequisites:
??????????????? adjacent[pre].append(cur)
??????????? for j in range(numCourses):
??????????????? if not dfs(j):
??????????????????? return []
??????????? return result[::-1]
leetcode269 火星詞典
??? 現(xiàn)有一種使用字母的全新語(yǔ)言,這門語(yǔ)言的字母順序與英語(yǔ)順序不同。
??? 假設(shè),您并不知道其中字母之間的先后順序。但是,會(huì)收到詞典中獲得一個(gè) 不為空的 單詞列表。因?yàn)槭菑脑~典中獲得的,所以該單詞列表內(nèi)的單詞已經(jīng) 按這門新語(yǔ)言的字母順序進(jìn)行了排序。
??? 您需要根據(jù)這個(gè)輸入的列表,還原出此語(yǔ)言中已知的字母順序。
??? 示例 1:
??????? 輸入:
??????? [
????????? "wrt",
????????? "wrf",
????????? "er",
????????? "ett",
????????? "rftt"
??????? ]
??????? ?
??????? 輸出: "wertf"
??? 示例 2:
??????? 輸入:
??????? [
????????? "z",
????????? "x"
??????? ]
??????? ?
??????? 輸出: "zx"
??????? ?
??????? 示例 3:
??? 輸入: [ "z", "x", "z" ]
??????? 輸出: ""
??????? ?
??????? 解釋: 此順序是非法的,因此返回 ""。
??? 注意:
??????? 你可以默認(rèn)輸入的全部都是小寫字母
??????? 假如,a 的字母排列順序優(yōu)先于 b,那么在給定的詞典當(dāng)中 a 定先出現(xiàn)在 b 前面
??????? 若給定的順序是不合法的,則返回空字符串即可
??????? 若存在多種可能的合法字母順序,請(qǐng)返回其中任意一種順序即可
從拓?fù)渑判虻慕嵌葋碚f,這個(gè)題其實(shí)不難,難點(diǎn)在于如何將詞典這一問題抽象成拓?fù)渑判?。?shí)際上,輸入所反映的字母的先后順序,也就是計(jì)算圖中節(jié)點(diǎn)的指向順序。
??? from collections import defaultdict, deque
??? class Solution:
??????? def alienOrder(self, words: List[str]) -> str:
??????????? ## 統(tǒng)計(jì)節(jié)點(diǎn)個(gè)數(shù)
??????????? nodes = set("".join(words))
??????????? ## 建立計(jì)算圖
??????????? adjacent = defaultdict(list)
??????????? indegree = dict(zip(list(nodes),[0] * len(nodes)))
??????????? for i in range(len(words) - 1):
??????????????? word1,word2 = words[i],words[i + 1]
??????????????? lenWord = min(len(word1),len(word2))
??????????????? for j in range(lenWord):
??????????????????? if word1[j] != word2[j]:
??????????????????????? adjacent[word1[j]].append(word2[j])
??????????????????????? indegree[word2[j]] += 1
??????????????????????? break
??????????? ## 拓?fù)渑判?br /> ??????????? result = []
??????????? queue = [i for i in indegree if indegree[i] == 0]
??????????? while queue:
??????????????? element = queue.pop()
??????????????? result.append(element)
??????????????? for neighbor in adjacent[element]:
??????????????????? indegree[neighbor] -= 1
??????????????????? if indegree[neighbor] == 0: queue.append(neighbor)
??? ?
??????????? return "".join(result) if len(result) == len(nodes) else ""
在結(jié)果是否有效的判定中,之前的判定條件是numCourses == 0,這里的判斷條件是len(result) == len(nodes),其實(shí)是一樣的,本質(zhì)都是判斷是否所有節(jié)點(diǎn)都被遍歷到。
leetcode261 以圖判樹
??? 給定從 0 到 n-1 標(biāo)號(hào)的 n 個(gè)結(jié)點(diǎn),和一個(gè)無向邊列表(每條邊以結(jié)點(diǎn)對(duì)來表示),請(qǐng)編寫一個(gè)函數(shù)用來判斷這些邊是否能夠形成一個(gè)合法有效的樹結(jié)構(gòu)。
??? 示例 1:
??????? 輸入: n = 5, 邊列表 edges = [[0,1], [0,2], [0,3], [1,4]]
??????? 輸出: true
??? 示例 2:
??????? 輸入: n = 5, 邊列表 edges = [[0,1], [1,2], [2,3], [1,3], [1,4]]
??????? 輸出: false
??? 注意: 你可以假定邊列表 edges 中不會(huì)出現(xiàn)重復(fù)的邊。由于所有的邊是無向邊,邊 [0,1] 和邊 [1,0] 是相同的,因此不會(huì)同時(shí)出現(xiàn)在邊列表 edges 中。
這個(gè)題和前面的問題有一個(gè)不同之處,就是前面的問題中,我們只需要考慮圖是否成環(huán)的問題,所有的點(diǎn)都必然是連通的。而這個(gè)問題中,我們還要考慮是否有不被其他點(diǎn)連通的點(diǎn)存在。
其他的思路幾乎相同,需要注意的就是判斷條件有兩個(gè):1.所有的點(diǎn)都聯(lián)通,這由所有的點(diǎn)都被遍歷過來判斷;2.沒有環(huán),這由邊的計(jì)數(shù)來判斷,和之前的numCourse對(duì)應(yīng)。
??? class Solution:
??????? def validTree(self, n: int, edges: List[List[int]]) -> bool:
??????????? adjacent = [[] for _ in range(n)]
??????????? for x,y in edges:
??????????????? adjacent[x].append(y)
??????????????? adjacent[y].append(x)
??????????? visited = set()
??? ?
??????????? def helper(prev,node):
??????????????? if node in visited: return False
??????????????? visited.add(node)
??????????????? for neighbor in adjacent[node]:
??????????????????? if neighbor == prev: continue
??????????????????? if not helper(node,neighbor): return False
??????????????? return True
??? ?
??????????? return helper(None,0) and len(visited) == n
另一種方法
??? class Solution:
??????? def validTree(self, n: int, edges: List[List[int]]) -> bool:
??????????? ##從一個(gè)點(diǎn)出發(fā),能遍歷所有的點(diǎn),保證的是連通性
??????????? ##無環(huán)性是這樣保證的:從一個(gè)點(diǎn)出發(fā),應(yīng)該只能到達(dá)另一個(gè)點(diǎn)一次
??????????? ##如果不重復(fù)路徑可以到達(dá)兩次,那就是有環(huán)
??????????? ##我們記錄下已經(jīng)遍歷過的點(diǎn),如果有環(huán),那么就會(huì)有已經(jīng)遍歷過的點(diǎn)再次出現(xiàn),也就是有一條邊沒有被從總數(shù)中減掉
??????????? ##那么就有l(wèi)enEdges != 0
??????????? ##這一套也可以改成遞歸,省個(gè)棧,改起來很容易
??????????? ##最后就是注意集合比列表快得多,如果不要求順序,還是優(yōu)先用這個(gè)
??????????? adjacent = [[] for _ in range(n)]
??????????? for x,y in edges:
??????????????? adjacent[x].append(y)
??????????????? adjacent[y].append(x)
??????????? visited = {0}
??????????? stack = [0]
??????????? lenEdges = len(edges)
??????????? while stack:
??????????????? element = stack.pop()
??????????????? for neighbor in adjacent[element]:
??????????????????? if not neighbor in visited:
??????????????????????? lenEdges -= 1
??????????????????????? visited.add(neighbor)
??????????????????????? stack.append(neighbor)
??????????? return len(visited) == n and lenEdges == 0?? ?
————————————————
版權(quán)聲明:本文為CSDN博主「菲菲小姐」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權(quán)協(xié)議,轉(zhuǎn)載請(qǐng)附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/weixin_39655021/article/details/103752005
總結(jié)
以上是生活随笔為你收集整理的leetcode阶段总结——拓扑排序的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 云朵是天空的脚印
- 下一篇: 【我要我的音乐】让我们红尘作伴活得潇潇洒