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,否则标记为-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 ;
    }
}

在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

nuist__NJUPT

给个鼓励吧,谢谢你

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值