• LeetCode - 207 课程表


    题目来源

    207. 课程表 - 力扣(LeetCode)

    题目描述

    你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 。

    在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [ai, bi] ,表示如果要学习课程 ai 则 必须 先学习课程  bi 。

    例如,先修课程对 [0, 1] 表示:想要学习课程 0 ,你需要先完成课程 1 。
    请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false 。

    示例

    输入numCourses = 2, prerequisites = [[1,0]]
    输出true
    说明总共有 2 门课程。学习课程 1 之前,你需要完成课程 0 。这是可能的。
    输入numCourses = 2, prerequisites = [[1,0],[0,1]]
    输出false
    说明总共有 2 门课程。学习课程 1 之前,你需要先完成​课程 0 ;并且学习课程 0 之前,你还应先完成课程 1 。这是不可能的。

    提示

    • 1 <= numCourses <= 10^5
    • 0 <= prerequisites.length <= 5000
    • prerequisites[i].length == 2
    • 0 <= ai, bi < numCourses
    • prerequisites[i] 中的所有课程对 互不相同

    题目解析

    我们可以根据题目要求画出一个有向图,比如下面用例

    numCourses = 4, prerequisites = [[2,1], [4,3], [4,2]]

    学2之前,必须先学1;

    学4之前,必须先学2,3

    学1,3没有前置课程。

    因此,通过上面有向图,我们可以得出,能够学完所有课程。

    而上面有向图其实就是一种拓扑结构,在拓扑结构中,每一个顶点都有“入度”概念,所谓入度,即当前顶点的直接前驱顶点数目

    如上图所示,

    顶点1的入度为0

    顶点2的入度为1

    顶点3的入度为0

    顶点4的入度为2,注意入度指的是当前顶点的直接前驱顶点个数,而顶点4的直接前驱只有2,3,因此顶点4的入度为2 。

    因此,在处理以上拓扑结构时,我们的入手点,就是入度为0的顶点。

    我们假设一个顶点入度为0,表示已经读完了该课程,因此我们假设去掉该顶点,则剩余顶点的入度需要被更新,如下图,1,3顶点被去除后,2的入度更新为0,4的入度更新为1

    因此,我们可能又会得到一批新的入度为0的顶点,然后重复以上步骤,直到没有入度为0的顶点,此时,若拓扑结构中,没有残留顶点,则说明,所有课程已经被读完,否则说明无法读完所有课程,比如下面用例

    numCourses = 4, prerequisites = [[2,1], [3,2], [4,3], [2,4]]

     

    当我们去除一个入度为0的顶点后,剩余顶点的入度没有为0的了,因此无法再去除顶点了,此时出现了环。 

    在了解了拓扑排序的基本知识后,我们就可以考虑如何实现以上过程:

    首先,我们需要记录每一个顶点的入度,即每一个顶点的直接前驱的个数

    然后,我们还需要记录每一个顶点A的直接后继顶点,因为,当顶点A的入度变为0时,模拟去除顶点A,即将顶点A的直接后继顶点们的入度减去1。

    在统计完以上信息后,我们就可以遍历所有顶点,找到入度为0的,作为入口条件,运行减度逻辑。

    JS算法源码

    1. /**
    2. * @param {number} numCourses
    3. * @param {number[][]} prerequisites
    4. * @return {boolean}
    5. */
    6. var canFinish = function (numCourses, prerequisites) {
    7. const inDegree = new Array(numCourses).fill(0);
    8. const next = {};
    9. // 要想学习a,先要学习b
    10. for (let [a, b] of prerequisites) {
    11. // a的入度+1
    12. inDegree[a]++;
    13. // b的后继节点集合添加a
    14. next[b] ? next[b].push(a) : (next[b] = [a]);
    15. }
    16. // 记录入度为0的点
    17. const queue = [];
    18. for (let i = 0; i < numCourses; i++) {
    19. if (inDegree[i] === 0) {
    20. queue.push(i);
    21. }
    22. }
    23. // 记录已完成的课程数目
    24. let complete = 0;
    25. while (queue.length > 0) {
    26. // 父课程完成
    27. const fa = queue.shift();
    28. complete++;
    29. next[fa]?.forEach((ch) => {
    30. // 父课程完成后,其后的子课程入度-1
    31. inDegree[ch] -= 1;
    32. // 如果子课程入度减少为0,则加入队列
    33. if (inDegree[ch] == 0) {
    34. queue.push(ch);
    35. }
    36. });
    37. }
    38. // 如果已学完的课程数等于总课程数,表示已经学完
    39. return complete === numCourses;
    40. };

    Java算法源码

    1. import java.util.ArrayList;
    2. import java.util.HashMap;
    3. import java.util.LinkedList;
    4. class Solution {
    5. public boolean canFinish(int numCourses, int[][] prerequisites) {
    6. int[] inDegree = new int[numCourses];
    7. HashMap> next = new HashMap<>();
    8. for (int[] prerequisite : prerequisites) {
    9. // 要想学习a,先要学习b
    10. int a = prerequisite[0];
    11. int b = prerequisite[1];
    12. // a的入度+1
    13. inDegree[a]++;
    14. // b的后继节点集合添加a
    15. next.putIfAbsent(b, new ArrayList<>());
    16. next.get(b).add(a);
    17. }
    18. // 记录入度为0的点
    19. LinkedList queue = new LinkedList<>();
    20. for (int i = 0; i < numCourses; i++) {
    21. if (inDegree[i] == 0) {
    22. queue.add(i);
    23. }
    24. }
    25. // 记录已完成的课程数目
    26. int complete = 0;
    27. while (queue.size() > 0) {
    28. // 父课程完成
    29. int fa = queue.removeFirst();
    30. complete += 1;
    31. if (next.containsKey(fa)) {
    32. for (int ch : next.get(fa)) {
    33. // 父课程完成后,其后的子课程入度-1
    34. inDegree[ch] -= 1;
    35. // 如果子课程入度减少为0,则加入队列
    36. if (inDegree[ch] == 0) {
    37. queue.add(ch);
    38. }
    39. }
    40. }
    41. }
    42. return complete == numCourses;
    43. }
    44. }

    Python算法源码

    1. class Solution(object):
    2. def canFinish(self, numCourses, prerequisites):
    3. """
    4. :type numCourses: int
    5. :type prerequisites: List[List[int]]
    6. :rtype: bool
    7. """
    8. inDegree = [0] * numCourses
    9. next = {}
    10. # 要想学习a,先要学习b
    11. for a, b in prerequisites:
    12. # a的入度+1
    13. inDegree[a] += 1
    14. # b的后继节点集合添加a
    15. next.setdefault(b, [])
    16. next.get(b).append(a)
    17. # 记录入度为0的点
    18. queue = []
    19. for i in range(numCourses):
    20. if inDegree[i] == 0:
    21. queue.append(i)
    22. # 记录已完成的课程数目
    23. complete = 0
    24. while len(queue) > 0:
    25. # 父课程完成
    26. fa = queue.pop(0)
    27. complete += 1
    28. if next.get(fa):
    29. for ch in next[fa]:
    30. # 父课程完成后,其后的子课程入度-1
    31. inDegree[ch] -= 1
    32. # 如果子课程入度减少为0,则加入队列
    33. if inDegree[ch] == 0:
    34. queue.append(ch)
    35. # 如果已学完的课程数等于总课程数,表示已经学完
    36. return complete == numCourses

    C算法源码

    1. typedef struct Node {
    2. int ele;
    3. struct Node *prev;
    4. struct Node *next;
    5. } ListNode;
    6. typedef struct {
    7. int size;
    8. ListNode *head;
    9. ListNode *tail;
    10. } LinkedList;
    11. LinkedList *new_LinkedList() {
    12. LinkedList *link = (LinkedList *) malloc(sizeof(LinkedList));
    13. link->size = 0;
    14. link->head = NULL;
    15. link->tail = NULL;
    16. return link;
    17. }
    18. void addLast_LinkedList(LinkedList *link, int ele) {
    19. ListNode *node = (ListNode *) malloc(sizeof(ListNode));
    20. node->ele = ele;
    21. node->prev = NULL;
    22. node->next = NULL;
    23. if (link->size == 0) {
    24. link->head = node;
    25. link->tail = node;
    26. } else {
    27. link->tail->next = node;
    28. node->prev = link->tail;
    29. link->tail = node;
    30. }
    31. link->size++;
    32. }
    33. int removeFirst_LinkedList(LinkedList *link) {
    34. if (link->size == 0) exit(-1);
    35. ListNode *removed = link->head;
    36. if (link->size == 1) {
    37. link->head = NULL;
    38. link->tail = NULL;
    39. } else {
    40. link->head = link->head->next;
    41. link->head->prev = NULL;
    42. }
    43. link->size--;
    44. return removed->ele;
    45. }
    46. bool canFinish(int numCourses, int **prerequisites, int prerequisitesSize, int *prerequisitesColSize) {
    47. int inDegree[2000] = {0};
    48. LinkedList *next[2000] = {NULL};
    49. for (int i = 0; i < prerequisitesSize; i++) {
    50. // 要想学习a,先要学习b
    51. int a = prerequisites[i][0];
    52. int b = prerequisites[i][1];
    53. // a的入度+1
    54. inDegree[a]++;
    55. // b的后继节点集合添加a
    56. if (next[b] == NULL) {
    57. next[b] = new_LinkedList();
    58. }
    59. addLast_LinkedList(next[b], a);
    60. }
    61. // 记录入度为0的点
    62. LinkedList *queue = new_LinkedList();
    63. for (int i = 0; i < numCourses; i++) {
    64. if (inDegree[i] == 0) {
    65. addLast_LinkedList(queue, i);
    66. }
    67. }
    68. // 记录已完成的课程数目
    69. int complete = 0;
    70. while (queue->size > 0) {
    71. // 父课程完成
    72. int fa = removeFirst_LinkedList(queue);
    73. complete += 1;
    74. if (next[fa] != NULL) {
    75. ListNode *cur = next[fa]->head;
    76. while (cur != NULL) {
    77. int ch = cur->ele;
    78. // 父课程完成后,其后的子课程入度-1
    79. inDegree[ch] -= 1;
    80. // 如果子课程入度减少为0,则加入队列
    81. if (inDegree[ch] == 0) {
    82. addLast_LinkedList(queue, ch);
    83. }
    84. // 如果已学完的课程数等于总课程数,表示已经学完
    85. cur = cur->next;
    86. }
    87. }
    88. }
    89. return complete == numCourses;
    90. }

  • 相关阅读:
    计算机毕业设计JavaWeb闲置服装交易平台(源码+系统+mysql数据库+lw文档)
    5. HBase必知必会之理论进阶篇
    安装umi4阻碍一天的问题解决了
    【EPLAN】统一修改项目中字体大小
    R语言方差分析总结
    Java源码分析 | Object
    JS事件循环机制
    react如何使用echars图标
    华为交换机ssh远程登录配置命令
    ElasticSearch 创建索引超时(ReadTimeoutError)
  • 原文地址:https://blog.csdn.net/qfc_128220/article/details/127804547