【LeetCode题目拓展】第207题 课程表 拓展(拓扑排序、Tarjan算法、Kosaraju算法)

文章目录

    • 一、拓扑排序题目
    • 二、题目拓展
      • 1. 思路分析
      • 2. tarjan算法
      • 3. kosaraju算法

一、拓扑排序题目

最近在看一个算法课程的时候看到了一个比较好玩的题目的扩展,它的原题如下:
在这里插入图片描述
对应的LeetCode题目为 207. 课程表

这个题目本身来说比较简单,就是一道标准的拓扑排序题目,解法代码如下:

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

public class Solution {
    public boolean scheduleCourses(int[][] prerequisites) {
        // 记录每个节点的入度
        int[] degree = new int[prerequisites.length]; 
        // 记录每个节点是哪些节点的前置节点
        List<Integer>[] neighbors = new List[prerequisites.length]; 
        // 记录当前可以选择的节点
        Queue<Integer> available = new LinkedList<>();

        for (int i = 0; i < prerequisites.length; i++) {
            degree[i] = prerequisites[i].length;
            neighbors[i] = new ArrayList<>();
            if (degree[i] == 0) {
                available.offer(i);
            }
        }

        for (int i = 0; i < prerequisites.length; i++) {
            for (int to : prerequisites[i]) {
                neighbors[to].add(i);
            }
        }

        int count = 0;
        while (!available.isEmpty()) {
            // 1. 取出available中一个节点
            // 2. 遍历该节点的邻居节点
            //   a. 将该邻居节点的入度减一
            //   b. 若此时邻居节点的入度为0,加入available中
            // 3. 处理节点数count加一
            int cur = available.poll();
            for (int to: neighbors[cur]) {
                degree[to]--;
                if (degree[to] == 0) {
                    available.offer(to);
                }
            }
            count++;
        }
        return count == prerequisites.length;
    }
}

二、题目拓展

这道题目整体难度不大,但是课程里提出了对于该题目的拓展非常有意思。
题目拓展:假如学生有同时上多门课的能力,那么是否可以根据他最多能同时上课的数量,来判断对于指定的课程安排他是否可以完成。

1. 思路分析

初看这个拓展时,我的想法是在有向图里找环的方式来实现,比如找到整个有向图中包含节点数目最多的环,判断这个数目是否超过了该同学最多能同时上课的数量。但这种方式有一个比较大的问题,就是如何定义什么是环,以及该定义下的环是否满足需要。根据这两个问题,我举出了如下两个图:
在这里插入图片描述
在这里插入图片描述
可以看到,对于第一个图来说,它可以说包含3个环,这种情况下该以哪个环的节点数来度量呢?对于第二个图,两个环共用了一个节点,此时只计算一个环的节点数并不能满足题目的需求。

此时仔细观察图二中的这种场景,我发现只有这两个环都计算节点数然后与可以最大同时上课数比较才成立。结合图一,可以得出一个结论,即当图中一个节点与另一个节点可以互相到达时,它们需要被计算在一起。这不正好就是有向图的强连通分量的定义吗?

于是,这个问题就转换成了,求一个有向图中包含节点数最大的连通分量的节点数。整个思路豁然开朗了。(由此可以看出,拿到一些特化的问题时把问题迁移到一种常识性问题是非常重要的!)

根据这种思路,我们需要求有向图中规模最大的连通分量的节点数,并且把它和学生最大同时上课数进行比较,就可以得到答案了。

详细图示如下:
在这里插入图片描述
将每一个强连通分量视为学生需要同时上的课程,即可以得到一个强连通分量缩点的拓扑排序,之后学生可以按照正常的拓扑排序顺序对缩点进行上课即可,如下所示:
在这里插入图片描述

求解有向图中的强连通分量问题一般有两种算法,tarjan算法和kosaraju算法,此处不赘述两种算法的细节,感兴趣的可以自行搜索,此处只把各自解法列在下方。

2. tarjan算法

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Stack;

public class TarjanSolution {
    // 图的邻接表表示形式,记录每个节点是哪些节点的前置节点
    private List<Integer>[] neighbors;
    private int skill;
    
    private int[] dfn;
    private int[] low;
    private int idx;

    private Stack<Integer> stack;
    private boolean[] isInStack; // 用于快速判断是否在栈中
    public boolean scheduleCourses(int[][] prerequisites, int skill) {
        if (skill < 1) {
            return false;
        }
        this.skill = skill;
        // 初始化相关存储结构
        initGraph(prerequisites);

        // 最大连通分量的节点数
        return tarjan();
    }

    private void initGraph(int[][] prerequisites) {
        neighbors = new List[prerequisites.length]; 
        for (int i = 0; i < prerequisites.length; i++) {
            neighbors[i] = new LinkedList<>();
        }
        for (int i = 0; i < prerequisites.length; i++) {
            for (int to : prerequisites[i]) {
                neighbors[to].add(i);
            }
        }
    }

    private boolean tarjan() {
        this.dfn = new int[neighbors.length];
        this.low = new int[neighbors.length];
        this.idx = 0;
        this.isInStack = new boolean[neighbors.length];
        this.stack = new Stack<Integer>();

        for (int i = 0; i < neighbors.length; ++i) {
            if (dfn[i] == 0) {
                if (!tarjanRecursion(i)) { // 如果已经失败,则提前结束
                    return false;
                }
            }
        }
        return true;
    }

    private boolean tarjanRecursion(int cur) {
        // 入栈
        stack.push(cur);
        isInStack[cur] = true;

        //初始化当前节点的时间戳
        dfn[cur] = low[cur] = ++idx;

        // 遍历当前节点的邻居节点,共3类:1. 没被找过的;2. 在栈里的;3. 已经构成联通分量的(这种直接跳过即可)
        for (int neighbor: neighbors[cur]) {
            // 如果没被找过
            if (dfn[neighbor] == 0) {
                if (!tarjanRecursion(neighbor)) { // 如果已经失败,则提前结束
                    return false;
                }
                low[cur] = Math.min(low[cur], low[neighbor]);
            } else if (isInStack[neighbor]) { // 在栈里
                low[cur] = Math.min(low[cur], dfn[neighbor]);
            }
        }

        int connectedComponentNodeNum = 0;
        // 若dfn==low,则当前已找到一个强连通分量,该分量节点为当前节点到栈顶的所有节点
        if (dfn[cur] == low[cur]) {
            while (cur != stack.peek()) { // 将所有非当前节点退栈
                int tmp = stack.pop();
                isInStack[tmp] = false;
                if (++connectedComponentNodeNum > skill) {
                    return false;
                }
                
            }
            // 把当前节点退栈
            stack.pop();
            isInStack[cur] = false;
            if (++connectedComponentNodeNum > skill) {
                return false;
            }
        }
        return true;
    }
}

3. kosaraju算法

import java.util.LinkedList;
import java.util.List;
import java.util.Stack;

public class KosarajuSolution {
    // 图的邻接表表示形式,记录每个节点是哪些节点的前置节点
    private List<Integer>[] neighbors;
    // 图的逆邻接表表示形式,记录每个节点是哪些节点的后置节点
    private List<Integer>[] rneighbors;
    private int skill;

    private boolean[] visited;
    private Stack<Integer> stack;

    public boolean scheduleCourses(int[][] prerequisites, int skill) {
        if (skill < 1) {
            return false;
        }
        this.skill = skill;
        // 初始化相关存储结构
        initGraph(prerequisites);

        // 最大连通分量的节点数
        return kosaraju();
    }

    private void initGraph(int[][] prerequisites) {
        neighbors = new List[prerequisites.length]; 
        rneighbors = new List[prerequisites.length]; 
        for (int i = 0; i < prerequisites.length; i++) {
            neighbors[i] = new LinkedList<>();
            rneighbors[i] = new LinkedList<>();
        }
        for (int i = 0; i < prerequisites.length; i++) {
            for (int to : prerequisites[i]) {
                neighbors[to].add(i);
                rneighbors[i].add(to);
            }
        }
    }

    private boolean kosaraju() {
        this.visited = new boolean[neighbors.length];
        this.stack = new Stack<Integer>();
        for (int i = 0; i < neighbors.length; ++i) { // 遍历正向图,记录出栈顺序
            if (!this.visited[i]) {
                kosarajuDfs1(i);
            }
        }
        while (!stack.isEmpty()) { // 从出栈最晚的节点开始,dfs遍历反向图
            int cur = stack.pop();
            if (this.visited[cur]) {
                if (kosarajuDfs2(cur) > skill) // 提前结束
                    return false;
            }
        }
        return true;
    }

    private void kosarajuDfs1(int cur) {
        this.visited[cur] = true;
        for (int next: this.neighbors[cur]) {
            if (!this.visited[next]) {
                kosarajuDfs1(next);
            }
        }
        stack.push(cur);
    }

    private int kosarajuDfs2(int cur) {
        this.visited[cur] = false;
        int count = 1;
        for (int pre: this.rneighbors[cur]) {
            if (this.visited[pre]) {
                if (count > this.skill) return count; // 提前结束
                count += kosarajuDfs2(pre);
            }
        }
        return count;
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/239624.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

单元测试技术

文章目录 一、单元测试快速入门二、单元测试断言三、Junit框架的常用注解 一、单元测试快速入门 所谓单元测试&#xff0c;就是针对最小的功能单元&#xff0c;编写测试代码对其进行正确性测试。 常规的例如如果在main中测试&#xff0c;比如说我们写了一个学生管理系统&…

【九】spring、springmvc、springboot、springcloud

spring、springmvc 、springboot 、springcloud 简介 从事IT这么些年&#xff0c;经历了行业技术的更迭&#xff0c;各行各业都会有事务更新&#xff0c;IT行业技术更迭速度快的特点尤为突出&#xff0c;或许这也是从事这个行业的压力所在&#xff0c;但另一方面反应了这个行业…

2023年第三季度全球SSD出货量环比增长24%,市场复苏!

根据Trendfocus发布的研究报告显示&#xff1a;2023年第三季度全球SSD出货量环比增长24%&#xff0c;达到9306万pcs&#xff0c;出货容量也增长了21%&#xff0c;达到7769EB。三星出货量市场TOP1&#xff0c;其次是WDC西部数据、金士顿、镁光Micron、海力士等。 由于PC OEM连续…

【精选】小白是如何挖漏洞的(技巧篇)

目录&#xff1a; 怎么找漏洞 找到后如何挖漏洞 关于通杀漏洞N day漏洞的挖掘 漏洞如何提交 每小结都有提供对应的案例&#xff0c;简直不要太nice&#xff01; 这个月的SRC活动也快开始了&#xff0c;看到群里的小伙伴在问如何找漏洞&#xff0c;SQL注入的漏洞咋找&#x…

jQuery遍历与删除添加节点

个人名片&#xff1a; &#x1f60a;作者简介&#xff1a;一名大二在校生 &#x1f921; 个人主页&#xff1a;坠入暮云间x &#x1f43c;座右铭&#xff1a;懒惰受到的惩罚不仅仅是自己的失败&#xff0c;还有别人的成功。 &#x1f385;**学习目标: 坚持每一次的学习打卡 文章…

极简壁纸js逆向(混淆处理)

本文仅用于技术交流&#xff0c;不得以危害或者是侵犯他人利益为目的使用文中介绍的代码模块&#xff0c;若有侵权请练习作者更改。 之前没学js&#xff0c;卡在这个网站&#xff0c;当时用的自动化工具&#xff0c;现在我要一雪前耻。 分析 第一步永远都是打开开发者工具进…

接口管理——Swagger

Swagger是一个用于设计、构建和文档化API的工具集。它包括一系列工具&#xff0c;如Swagger Editor&#xff08;用于编辑Swagger规范&#xff09;、Swagger UI&#xff08;用于可视化API文档&#xff09;和Swagger Codegen&#xff08;用于根据API定义生成客户端库、server stu…

【FPGA】Verilog:BCD 加法器的实现 | BCD 运算 | Single-level 16 bit 超前进位加法器 | 2-level 16-bit 超前进位加法器

0x00 BCD 运算 在 BCD 中,使用4位值作为操作数,但由于只表示 0 到 9 的数字,因此只使用 0000 到 1001 的二进制数,而不使用 1010 到 1111 的二进制数(dont care)。 因此,不能使用常规的 2complement 运算来计算,需要额外的处理:如果 4 位二进制数的运算结果在 1010 …

Rxjs 学习笔记 - 简化版

RxJS 提供了一套完整的异步解決方案&#xff0c;让我们在面对各种异步行为&#xff08;Event, AJAX, Animation 等&#xff09;都可以使用相同的 API 做开发。 前置知识点 同步&#xff08;Synchronous&#xff09;就是整个处理过程顺序执行&#xff0c;当各个过程都执行完毕&…

【acwing】92. 递归实现指数型枚举

穿越隧道 递归枚举、位运算 方法① 从1到n&#xff0c;顺序访问每位数&#xff0c;是否选择&#xff0c;每位数有两种状态&#xff0c;选1或不选0. AC代码如下&#xff1a; #include <iostream> using namespace std;const int N 100; // bool st[N]; int n;void dfs(in…

JVM面试

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 1.JVM 的整体结构2.类加载做了哪些事情?类加载器有哪些&#xff1f;双亲委派和沙箱安全 3.Java虚拟机栈是什么4.方法区的理解HotSpot 中方法区的演进方法区的内部结…

XSS漏洞 深度解析 XSS_labs靶场

XSS漏洞 深度解析 XSS_labs靶场 0x01 简介 XSS原名为Cross-site Sciprting(跨站脚本攻击)&#xff0c;因简写与层叠样式表(Cascading style sheets)重名&#xff0c;为了区分所以取名为XSS。 这个漏洞主要存在于HTML页面中进行动态渲染输出的参数中&#xff0c;利用了脚本语…

git常规操作流程(纯命令行操作)和一些注意事项

当你在单位拿到了git仓库,并利用公司给你的OA账号和邮箱完成了你的git基础配置,下面就是使用命令行的无错固定操作流程 如果你很着急,你可以直接跳到最后的总结部分 具体步骤 1.从仓库克隆代码到本地 这里的[codeUrl]就是你仓库的地址,当你在仓库点击图中绿色位置时,剪贴板…

1841_在Windows上安装emacs irony server

Grey 全部学习内容汇总&#xff1a;GitHub - GreyZhang/editors_skills: Summary for some common editor skills I used. 1841_在Windows上安装emacs irony server emacs有很多优点&#xff0c;配置出来不仅用着顺手而且有一定的成就感。但是&#xff0c;对于大多数人来说或…

001两数之和

题意 给出一个数组和一个目标值&#xff0c;让你在数组中找出和为目标值的两个数&#xff0c;并且这两个数在数组中的下标&#xff08;索引&#xff09;不同。 示例 输入&#xff1a;nums[2,7,11,15],target9 输出&#xff1a;[0,1] 解释&#xff1a;因为nums[0]nums[1]9&#…

苹果app应用ipa文件程序开发后如何运行到苹果iOS真机上测试?

在苹果应用程序开发过程中&#xff0c;将app安装于真机进行测试是一个不可或缺的步骤&#xff0c;它可以帮助你检测app在实际设备上的性能表现及存在的潜在问题。这篇文章将详细阐述如何将开发好的苹果app&#xff08;.ipa文件&#xff09;安装到真机上进行测试。 图片来源&…

DataFunSummit:2023年数据治理在线峰会-核心PPT资料下载

一、峰会简介 数据治理&#xff08;Data Governance&#xff09;是组织中涉及数据使用的一整套管理行为。由企业数据治理部门发起并推行&#xff0c;关于如何制定和实施针对整个企业内部数据的商业应用和技术管理的一系列政策和流程。 数据治理是一个通过一系列信息相关的过程…

【数据结构】堆的模拟实现

前言:前面我们学习了顺序表、单链表、栈、队列&#xff0c;今天我们就开始新的学习吧&#xff0c;今天我们将进入堆的学习&#xff01;(最近博主处于低谷期)一起加油吧各位。 &#x1f496; 博主CSDN主页:卫卫卫的个人主页 &#x1f49e; &#x1f449; 专栏分类:数据结构 &…

idea__SpringBoot微服务10——整合JDBC(新依赖)

整合JDBC 完整项目地址&#xff1a;一、创建一个项目二、idea配置连接mysql三、创建yaml数据库连接配置文件四、测试一下&#xff0c;没有问题五、增删改查————————创作不易&#xff0c;如觉不错&#xff0c;随手点赞&#xff0c;关注&#xff0c;收藏(*&#xffe3;︶…

P4 Qt基础控件——工具按钮toolButton(上)

前言 &#x1f3ac; 个人主页&#xff1a;ChenPi &#x1f43b;推荐专栏1: 《C_ChenPi的博客-CSDN博客》✨✨✨ &#x1f525; 推荐专栏2: 《Linux C应用编程&#xff08;概念类&#xff09;_ChenPi的博客-CSDN博客》✨✨✨ &#x1f33a;本篇简介 &#xff1a;这一章我们学一…
最新文章