leetcode之拓扑排序刷题总结1
Posted nuist__NJUPT
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了leetcode之拓扑排序刷题总结1相关的知识,希望对你有一定的参考价值。
leetcode之拓扑排序刷题总结1
在计算机科学领域,有向图的拓扑排序(Topological Sort)是其顶点的线性排序,使得对于从顶点 u 到顶点 v 的每个有向边 u->v,u 在排序中都在 v 之前。
例如,图形的顶点可以表示要执行的任务,并且边可以表示一个任务必须在另一个任务之前执行的约束;在这个应用中,拓扑排序只是一个有效的任务顺序。
如果且仅当图形没有定向循环,即如果它是有向无环图(DAG),则拓扑排序是可能的。
任何 DAG 具有至少一个拓扑排序,存在算法用于在线性时间内构建任何 DAG 的拓扑排序。
1-喧闹和富有
题目链接:题目链接戳这里!!!
这题意味深长,知道的是考的拓朴排序和记忆化dfs,不知道还以为是阅读理解,请问leetcode出题人想要表达什么样的思想感情?答:穷在闹市无人问 ,富在深山有远亲 ,不信你看杯中酒 ,杯杯先敬有钱人。
思路1:邻接表+记忆化dfs
把richer数组按照由没钱的指向有钱的,构建邻接表,也就是一张有向无环图,对于每个节点,所指向的都是比自己有钱的,搜索每个节点的相邻节点,如果安静值比自己小,则更新当前节点的答案。
总的来说,最安静的人要么是 x 自己,要么是 x 的邻居的中最安静的人。
class Solution
public int[] loudAndRich(int[][] richer, int[] quiet)
int m = quiet.length ;
int [] ans = new int [m] ;
Arrays.fill(ans,-1) ;
List<Integer> [] g = new List[m] ;
for(int i=0; i<m; i++)
g[i] = new ArrayList<>() ;
for(int [] rich : richer)
g[rich[1]].add(rich[0]) ;
for(int i=0; i<m; i++)
dfs(g,i,ans,quiet) ;
return ans ;
public void dfs(List<Integer> [] g, int i, int [] ans, int [] quiet)
if(ans[i]!=-1)
return ;
ans[i] = i ;
for(int j : g[i])
dfs(g,j,ans,quiet) ;
if(quiet[ans[j]] < quiet[ans[i]])
ans[i] = ans[j] ;
思路2:邻接表+拓扑排序
拓扑排序简单来说,是对于一张有向图 G,我们需要将 G 的 n 个点排列成一组序列,使得图中任意一对顶点 <u,v>,如果图中存在一条 u→v 的边,那么 u 在序列中需要出现在 v 的前面。
按照下图所示,构建邻接表,就是按照richer构建,由有钱的指向没有钱的,圆形中的数字代表当前人物编号,旁边的数字代表每个人的安静值。
所以,你可以看到对于 2、5、4、6节点来说,他们在 answer 中的结果就是他们自己,因为,没有人比他们更有钱了,只能选他们自己。
而对于 3 来说,比他更有钱的有 5、4、6、3(需要包含他自己),在这几个节点中找安静值最小的,也就是 5 号,所以,answer[3] = 5。
然后,对于 1 来说,比他更有钱的有 2、3、1、5、4、6,但是,5、4、6 对于结果的贡献值已经传递给 3 了,所以,对于 answer[3] 我们在计算 1 的时候是可以直接利用的,也就是说计算 1 的时候并不需要看 5、4、6 的值了。
同理,可以得到其他所有节点的 answer 值。
这个过程呢,我们就可以使用拓扑排序来实现。
class Solution
public int[] loudAndRich(int[][] richer, int[] quiet)
int n = quiet.length ;
List<Integer> [] g = new List[n] ;
int [] inDegree = new int [n] ;
for(int i=0; i<n; i++)
g[i] = new ArrayList<>() ;
for(int [] rich : richer)//构建邻接表
g[rich[0]].add(rich[1]) ;
inDegree[rich[1]] ++ ; //统计入度
int [] ans = new int [n] ;
for(int i=0; i<n; i++) //初始化答案数组为自己
ans[i] = i ;
Queue<Integer> queue = new LinkedList<>() ;
for(int i=0; i<n; i++)
if(inDegree[i]==0) //入度为0的入队
queue.add(i) ;
while(!queue.isEmpty())
int p = queue.poll() ;
for(int q : g[p])
if(quiet[ans[p]]<quiet[ans[q]])
ans[q] = ans[p] ; //找到比自己有钱且比自己更安静的
if(--inDegree[q]==0)
queue.add(q) ;
return ans ;
2-课程表
题目链接:题目链接戳这里!!!
思路1:邻接表+dfs
按照课程的学习顺序构建一张邻接表,dfs搜索观察是否有环,如果有环,则返回false,无环则返回true。
class Solution
public boolean canFinish(int numCourses, int[][] prerequisites)
//邻接表+dfs
List<List<Integer>> edges = new ArrayList<>() ;
int [] vis = new int [numCourses] ;
for(int i=0; i<numCourses; i++)
edges.add(new ArrayList<>()) ;
for(int [] edge : prerequisites) //构建邻接表
edges.get(edge[1]).add(edge[0]) ;
for(int i=0; i<numCourses; i++)
if(!dfs(edges,i,vis))
return false ;
return true ;
public boolean dfs(List<List<Integer>> edges, int x, int [] vis)
if(vis[x] == 1) //有环
return false ;
if(vis[x]==-1) //无环
return true ;
vis[x] = 1 ;
for(int y : edges.get(x))
if(!dfs(edges,y,vis)) //有环
return false ;
vis[x] = -1 ;
return true ;
思路2:邻接表+拓扑排序
按照课程学习顺序构建一张邻接表,每次让入度为0的节点入队,每次记录出队节点数,如果出队节点数等于课程数,则可以完成所有课程的学习,如果存在环,则出队节点数不等于课程数,返回false。
class Solution
public boolean canFinish(int numCourses, int[][] prerequisites)
//邻接表+拓扑排序
List<List<Integer>> edges = new ArrayList<>() ;
int [] inDegree = new int [numCourses] ;
for(int i=0; i<numCourses; i++)
edges.add(new ArrayList<>()) ;
for(int [] edge : prerequisites) //构建邻接表
edges.get(edge[1]).add(edge[0]) ;
inDegree[edge[0]] ++ ;
int vis = 0 ;
Queue<Integer> queue = new LinkedList<>() ;
for(int i=0; i<numCourses; i++)
if(inDegree[i]==0) //入度为0的节点入队
queue.add(i) ;
while(!queue.isEmpty()) //队不空,则循环
vis ++ ; //记录出队节点数
int x = queue.poll() ;
for(int y : edges.get(x))
if(--inDegree[y] == 0)
queue.add(y) ;
return numCourses==vis ;
3-课程表II
思路:题目链接戳这里!!!
思路1:邻接表+dfs+栈
这个上一题的课程表思路一样,都是构建临界表,dfs搜索判断是否有环,如果有环则返回空数组,如果无环,用栈记录节点,从栈底到栈顶的元素就是一种满足条件的拓扑排序。
class Solution
public int[] findOrder(int numCourses, int[][] prerequisites)
List<List<Integer>> edge = new ArrayList<>() ;
Stack<Integer> stack = new Stack<>() ;
for(int i=0; i<numCourses; i++)
edge.add(new ArrayList<>()) ;
int [] vis = new int [numCourses] ;
for(int [] courses : prerequisites)
edge.get(courses[1]).add(courses[0]) ;
for(int i=0; i<numCourses; i++)
if(!dfs(edge,i,vis,stack))
return new int[] ;
int [] arr = new int [numCourses] ;
int k = 0 ;
while(!stack.isEmpty())
arr[k] = stack.pop() ;
k++ ;
return arr ;
public boolean dfs(List<List<Integer>> edge, int i, int []vis, Stack<Integer>stack)
if(vis[i]==1)
return false ;
if(vis[i]==-1)
return true ;
vis[i] = 1 ;
for(int j : edge.get(i))
if(!dfs(edge,j,vis,stack))
return false ;
vis[i] = -1 ;
stack.push(i) ;
return true ;
思路2:邻接表+拓扑排序
按照课程的先后顺序建立邻接表,并统计每个节点的入度,每一次入度为0的入队,如果队不空,则出队,并记录出队数量,如果出队节点数等于课程数量,说明满足要求,可以完成所有课程的学习,记录每次入队的节点就是一个满足条件的拓扑排序。
class Solution
public int[] findOrder(int numCourses, int[][] prerequisites)
List<List<Integer>> edge = new ArrayList<>() ;
int [] inDegree = new int [numCourses] ;
for(int i=0; i<numCourses; i++)
edge.add(new ArrayList<>()) ;
for(int [] courses : prerequisites) //构建邻接表
edge.get(courses[1]).add(courses[0]) ;
inDegree[courses[0]] ++ ; //记录入度
Queue<Integer> queue = new LinkedList<>() ;
int [] res = new int [numCourses] ;
int k = 0 ;
for(int i=0; i<numCourses; i++)
if(inDegree[i]==0)
queue.add(i) ;
res[k++] = i ;
int vis = 0 ;
while(!queue.isEmpty())
vis ++ ;
int x = queue.poll() ;
for(int y : edge.get(x))
if(--inDegree[y]==0)
queue.add(y) ;
res[k++] = y ;
if(vis!=numCourses)
return new int [] ;
else
return res ;
4-课程表IV
题目链接:题目链接戳这里!!!
思路1:邻接表+记忆化搜索
按照课程学习顺序建立邻接表,建立邻接表的过程中同时memo[x][y]=1标记x可以到达y,每轮搜索用memo[x][y]标记从x能否到达y,如果能过到达标记为1,否
以上是关于leetcode之拓扑排序刷题总结1的主要内容,如果未能解决你的问题,请参考以下文章