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的主要内容,如果未能解决你的问题,请参考以下文章

leetcode之哈希表刷题总结1

leetcode之深度优先搜索刷题总结1

leetcode之模拟刷题总结1

leetcode之数论与模拟刷题总结1

LeetCode刷题之合并排序链表

leetcode之回溯刷题总结1