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,否则标记为-1,
class Solution {
int [][] memo ;
public List<Boolean> checkIfPrerequisite(int numCourses, int[][] prerequisites, int[][] queries) {
List<List<Integer>> edges = new ArrayList<>() ;
List<Boolean> res = new ArrayList<>() ;
memo = new int[numCourses][numCourses] ;
for(int i=0; i<numCourses; i++){
edges.add(new ArrayList<>()) ;
}
for(int [] course : prerequisites){
edges.get(course[0]).add(course[1]) ;
memo[course[0]][course[1]] = 1 ;
}
for(int i=0; i<queries.length; i++){
boolean ans = dfs(edges,queries[i][0],queries[i][1]);
res.add(ans) ;
}
return res ;
}
public boolean dfs(List<List<Integer>> edges, int x, int y){
if(memo[x][y]==1){
return true ;
}
if(memo[x][y]==-1){
return false ;
}
for(int mid : edges.get(x)){
if(dfs(edges,mid,y)){
memo[x][y] = 1 ;
return true ;
}
}
memo[x][y] = -1 ;
return false ;
}
}
思路2:邻接表+拓扑排序
这个题要是建立邻接表后,直接dfs,会超时,需要使用记忆化dfs,当然这个思路是使用拓扑排序,就是使用memo记录所有的课程之间是否联通。
class Solution {
boolean [][] memo ;
public List<Boolean> checkIfPrerequisite(int numCourses, int[][] prerequisites, int[][] queries) {
List<List<Integer>> edges = new ArrayList<>() ;
List<Boolean> res = new ArrayList<>() ;
memo = new boolean[numCourses][numCourses] ;
for(int i=0; i<numCourses; i++){
edges.add(new ArrayList<>()) ;
}
for(int [] course : prerequisites){
edges.get(course[0]).add(course[1]) ;
}
for(int i=0; i<numCourses; i++){
Queue<Integer> queue = new LinkedList<>() ;
queue.add(i) ;
while(!queue.isEmpty()){
int x = queue.poll() ;
for(int y : edges.get(x)){
if(!memo[i][y]){
memo[i][y] = true ;
queue.add(y) ;
}
}
}
}
for(int querie [] : queries){
res.add(memo[querie[0]][querie[1]]) ;
}
return res ;
}
}
5-并行课程III
题目链接:题目链接戳这里!!!
思路:邻接表+拓扑排序+dp
我建立了邻接表和逆邻接表,对于每个节点i,dp[i]表示学习完第i+1门课程所需的最少时间,对于每个入度为0的节点,dp[i]=time[i],初始化入度为0的节点全部入队,只要队不空,对于队中元素,对于每个元素,先遍历逆邻接表,找出最大值max,再加上time[temp],就是当前节点学完所需的最少时间。
class Solution {
public int minimumTime(int n, int[][] relations, int[] time) {
List<List<Integer>> edges1 = new ArrayList<>() ; //邻接表
List<List<Integer>> edges2 = new ArrayList<>() ; //逆邻接表
int [] inDegree = new int [n] ;
int [] dp = new int [n] ;
for(int i=0; i<n; i++){
edges1.add(new ArrayList<>()) ;
edges2.add(new ArrayList<>()) ;
}
for(int []course : relations){
edges1.get(course[0]-1).add(course[1]-1) ;
edges2.get(course[1]-1).add(course[0]-1) ;
inDegree[course[1]-1] ++ ;
}
Queue<Integer> queue = new LinkedList<>() ;
for(int i=0; i<n; i++){
if(inDegree[i]==0){
queue.add(i) ;
dp[i] = time[i] ;
}
}
int max = 0 ;
int res = 0 ;
while(!queue.isEmpty()){
int len = queue.size() ;
for(int i=0; i<len; i++){
max = 0 ;
int temp = queue.poll() ;
for(int x : edges2.get(temp)){
max = Math.max(dp[x],max) ;
}
dp[temp] = max + time[temp] ;
res = Math.max(dp[temp],res) ;
for(int y : edges1.get(temp)){
if(--inDegree[y]==0){
queue.add(y) ;
}
}
}
}
return res ;
}
}