leetcode
leetcode 2951 ~ 3000
课程表 II

课程表 II

难度:

标签:

题目描述

English description is not available for the problem. Please switch to Chinese.

代码结果

运行时间: 27 ms, 内存: 17.2 MB


/*
思路:
1. 使用拓扑排序算法来解决这个问题。我们需要计算每门课程的入度(需要先修的课程数),并记录每门课程的后续课程。
2. 从入度为0的课程开始,逐一学习这门课程,并将其后续课程的入度减1。
3. 如果某门课程的入度减到0,则说明它可以被学习,继续将其加入学习序列。
4. 最终如果学习了所有课程,返回学习顺序,否则返回空数组。
*/

import java.util.*;
import java.util.stream.*;

public class Solution {
    public int[] findOrder(int numCourses, int[][] prerequisites) {
        List<Integer> result = new ArrayList<>();
        int[] indegree = new int[numCourses];
        Map<Integer, List<Integer>> graph = IntStream.range(0, numCourses).boxed().collect(Collectors.toMap(i -> i, i -> new ArrayList<>()));
        
        // 初始化图和入度数组
        Arrays.stream(prerequisites).forEach(prereq -> {
            int course = prereq[0];
            int prereqCourse = prereq[1];
            graph.get(prereqCourse).add(course);
            indegree[course]++;
        });
        
        // 找到所有入度为0的课程
        Queue<Integer> queue = IntStream.range(0, numCourses).filter(i -> indegree[i] == 0).boxed().collect(Collectors.toCollection(LinkedList::new));
        
        // 开始学习课程
        while (!queue.isEmpty()) {
            int course = queue.poll();
            result.add(course);
            graph.get(course).forEach(nextCourse -> {
                indegree[nextCourse]--;
                if (indegree[nextCourse] == 0) {
                    queue.offer(nextCourse);
                }
            });
        }
        
        // 检查是否所有课程都被学习
        return result.size() == numCourses ? result.stream().mapToInt(i -> i).toArray() : new int[0];
    }
}

解释

方法:

这个题解使用了拓扑排序的方法来安排课程的学习顺序。首先构建一个图和一个入度数组,图用于表示每门课作为先决条件时,会影响哪些课程,入度数组记录每门课程所需的先决条件数量。初始化时,将所有入度为0的课程(即没有先决条件的课程)加入队列。然后进行拓扑排序,每次从队列中取出一个课程,将其加入排序结果中,并减少其影响的课程的入度。如果减少后某课程的入度变为0,则将其加入队列。在遍历完所有课程后,如果排序的课程数量等于总课程数,则返回排序结果,否则说明存在循环依赖,返回空数组。

时间复杂度:

O(V + E)

空间复杂度:

O(V + E)

代码细节讲解

🦆
在构建图和入度数组时,你是如何处理重复的先决条件关系的?例如,如果存在多个相同的 [ai, bi],会对算法的执行或结果产生什么影响?
在构建图和入度数组时,如果存在重复的先决条件关系 [ai, bi],这意味着课程 bi 需要课程 ai 完成。因为我们是在遍历先决条件列表时直接更新图和入度数组,重复的 [ai, bi] 会导致 bi 的入度被重复增加。这会错误地表示 bi 有更多的先决条件要完成。因此,算法会错误地推迟将 bi 加入到可完成课程队列中,这可能会导致拓扑排序失败并错误地返回空数组,即使实际上不存在依赖循环。为了避免这种情况,我们应该在添加边和更新入度前检查是否已经添加过相同的先决条件。
🦆
在拓扑排序的过程中,如果存在多个课程同时入度为0,算法是如何选择下一个课程的?这种选择方式是否会影响最终的课程顺序?
在拓扑排序的过程中,当存在多个课程的入度为0时,选择下一个课程是基于队列的先进先出(FIFO)原则。具体地,先被检测到入度为0的课程会先被加入队列,并且将先从队列中被取出用于排序。这种选择方式确实会影响最终的课程顺序,因为不同的入队顺序会导致不同的课程被先处理。然而,所有可能的课程顺序都是有效的,因为它们都满足先决条件的约束。
🦆
你提到如果最终排序的课程数量不等于总课程数时,将返回空数组。这是否暗示了图中存在环?如何通过算法准确地检测到这种环的存在?
是的,如果最终排序的课程数量不等于总课程数,这意味着图中存在环。在拓扑排序中,只有当一个课程的所有先决条件已被满足(即入度为0),该课程才会被加入到排序中。如果图中存在环,则至少有一门课程在环中,其入度永远不会变为0。因此,这些课程不会被加入到排序结果中,导致排序结果中的课程数量少于总课程数。算法通过检测最终排序结果中的课程数量与总课程数是否相等来准确地检测出环的存在。如果不相等,表明存在环。

相关问题