LeetCode -- Course Schedule

本文探讨了如何通过拓扑排序解决课程安排问题,确保所有课程可以在无冲突的情况下完成。文章详细介绍了拓扑排序的概念、步骤及应用实例,并针对一个具体问题提供了实现代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

There are a total of n courses you have to take, labeled from 0 to n - 1.

Some courses may have prerequisites, for example to take course 0 you have to first take course 1, which is expressed as a pair: [0,1]

Given the total number of courses and a list of prerequisite pairs, is it possible for you to finish all courses?

For example:

2, [[1,0]]
There are a total of 2 courses to take. To take course 1 you should have finished course 0. So it is possible.

2, [[1,0],[0,1]]
There are a total of 2 courses to take. To take course 1 you should have finished course 0, and to take course 0 you should also have finished course 1. So it is impossible.

Note:
The input prerequisites is a graph represented by a list of edges, not adjacency matrices. Read more about how a graph is represented.

查阅题目知:实际上是求一个图中是否存在环。可以考虑使用拓扑排序。
拓扑排序:
拓扑排序(Topological Order)是指,将一个有向无环图(Directed Acyclic Graph简称DAG)进行排序进而得到一个有序的线性序列。
这样说,可能理解起来比较抽象。下面通过简单的例子进行说明!
例如,一个项目包括A、B、C、D四个子部分来完成,并且A依赖于B和D,C依赖于D。现在要制定一个计划,写出A、B、C、D的执行顺序。这时,就可以利用到拓扑排序,它就是用来确定事物发生的顺序的。
在拓扑排序中,如果存在一条从顶点A到顶点B的路径,那么在排序结果中B出现在A的后面。
拓扑排序算法的基本步骤:
1. 构造一个队列Q(queue) 和 拓扑排序的结果队列T(topological);
2. 把所有没有依赖顶点的节点放入Q;
3. 当Q还有顶点的时候,执行下面步骤:
3.1 从Q中取出一个顶点n(将n从Q中删掉),并放入T(将n加入到结果集中);
3.2 对n每一个邻接点m(n是起点,m是终点);
3.2.1 去掉边

public class Solution {
    /*
    * 拓扑排序
    * 1. 选择入度为0的点
    * 2. 将选中的点从图中去掉
    */
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        //图使用邻接矩阵表示
        int[][] E = new int[numCourses][numCourses]; 
        //顶点的入度
        long[] degree = new long[numCourses];

        if(numCourses <= 1 ){
            return true;
        }

        //构造图矩阵
        //E[i][j]表示i的前驱是j
        for(int i = 0 ; i < prerequisites.length ; i ++){
            E[prerequisites[i][0]][prerequisites[i][1]] = 1;
            degree[prerequisites[i][1]] ++;
        }

        //初始化入度
        for(int j = 0;j < numCourses; j ++){
           for(int i = 0; i < numCourses;i++){
               if(E[i][j] == 1){
                   degree[j] ++;
               }
           } 
        }
        //判断
        while(true){
            int var = select0(degree);
            if(var > -1){
                delete0(var,E,degree);
            }else{
                break;
            }
        }

        for(int i = 0; i < degree.length;i ++){
            if(degree[i] != Long.MAX_VALUE){
                return false;
            }    
        }
        return true;
    }
    //选择入度为0
    public int select0(long[] degree){
        for(int i = 0; i < degree.length;i++){
            if(degree[i] == 0){
                //degree[i] = Long.MAX_VALUE;
                return i;
            }
        }
        return -1;
    }
    //将点从图中去掉
    public void delete0(int var, int[][] E,long[] degree){

        degree[var] = Long.MAX_VALUE;

        for(int j = 0; j < E.length;j++){
            if(E[var][j] == 1){

                degree[j]--;
            }
        }
    }
}

尝试之后显示Status: Time Limit Exceeded

参阅网上的解法,显然都是用拓扑排序的思想,那么自己的程序速度在哪慢了?

//构造图矩阵
        //E[i][j]表示i的前驱是j
        for(int i = 0 ; i < prerequisites.length ; i ++){
            E[prerequisites[i][0]][prerequisites[i][1]] = 1;
            degree[prerequisites[i][1]] ++;
        }

        //初始化入度
        for(int j = 0;j < numCourses; j ++){
           for(int i = 0; i < numCourses;i++){
               if(E[i][j] == 1){
                   degree[j] ++;
               }
           } 
        }

显然在构造邻接矩阵的时候就可以构造出入度数组,如

for(int i = 0 ; i < prerequisites.length ; i ++){
            if(E[prerequisites[i][0]][prerequisites[i][1]] == 0){
                 E[prerequisites[i][0]][prerequisites[i][1]] = 1;
                 degree[prerequisites[i][1]] ++;
            }

        }

需要注意的是:prerequisite[i][j]可能会出现相同的序列对,如{{5,8},{3,5},{1,9},{4,5},{0,2},{1,9},{7,8},{4,9}};

<think>好的,我现在需要处理用户的问题。用户之前提到的是关于Leetcode编程练习题的需求,特别是参考了题目“字符串压缩”的引用。现在用户可能想寻找更多类似的编程练习题。首先,我要确认用户的具体需求是什么。他们可能需要不同类型的题目,比如字符串处理、数组、链表、动态规划等,或者特定难度的题目。此外,用户可能希望得到题目的分类推荐,以便有针对性地练习。 接下来,我需要考虑如何组织回答。根据系统指令,回答需要结构清晰,分步骤解决问题。可能需要先分类Leetcode题目,然后推荐每个类别的经典题目,并附上链接。同时,要确保所有数学公式正确使用LaTeX,行内公式用$...$,独立公式用$$...$$,但在这个问题中可能数学公式较少,不过如果有算法相关的内容,比如时间复杂度,可能需要用到。 然后,生成相关问题部分。需要根据回答内容提出相关问题,比如如何准备算法面试,如何优化特定类型的题目等。还要注意引用标记,用户之前的引用是题号[^1],所以如果有新的引用需要添加,可能需要检查是否有其他引用来源,但根据现有信息,主要引用的是Leetcode题目,所以可能每个题目后面都加上引用标识,比如[^2], [^3]等,但需要确认用户提供的引用是否有多个。不过用户提供的引用只有,即面试题01.06,所以其他题目可能需要创建新的引用,但根据系统指令中的示例,用户提供的引用可能只有那个,因此可能需要假设其他引用是用户已知的,或者避免添加过多引用。或者,可能只需要在回答中提到的题目后面添加对应的Leetcode链接作为引用,但根据示例中的引用,用户可能希望使用脚注形式。例如,每个题目后面加上[^2], [^3],并在回答末尾列出这些引用。 但根据用户提供的引用示例,他们只给了一个引用,即对应字符串压缩题目。所以在回答中提到的其他题目需要创建新的引用,但用户没有提供其他来源,可能需要自行补充,或者只引用用户提供的那个。但实际情况中,Leetcode每个题目都有独立链接,因此可能需要为每个推荐的题目添加引用链接,即使它们不在用户提供的引用中。不过根据系统指令,用户可能希望引用他们提供的站内引用,即只引用[^1]。因此,可能需要避免添加其他引用,或者在无法避免时,使用其他方式,比如直接提供链接而不使用脚注。但示例中用户允许添加引用标识,比如在示例中的快速排序引用了,但实际引用内容可能不相关。这里可能存在矛盾,因为用户提供的引用是字符串压缩题目,但回答中提到的其他题目可能需要不同的引用。因此,可能的解决方案是在回答中提到的每个题目后面添加对应的Leetcode链接作为超链接,而不是使用脚注,或者使用脚注并假设用户能理解这些是额外的引用。但根据系统指令中的示例,引用标识是添加在段落末尾,例如,所以可能需要为每个题目创建一个引用,即使它们不在用户提供的引用列表中。但用户只提供了一个引用,所以可能无法这样做。因此,可能需要调整回答方式,避免需要多个引用,或者直接列出题目名称和链接,而不使用脚注。或者,在回答中使用说明性的链接,例如将题目名称直接链接到Leetcode页面。 综合考虑,可能最佳做法是列出各个题目,并在每个题目后面用括号附上链接,不使用脚注,因为用户只提供了一个引用,而其他题目需要不同的链接。但根据系统指令,回答中引用的段落末尾需要自然地添加引用标识,例如。但这里的引用可能指的是用户提供的引用,而其他题目可能需要新的引用标识,但用户并没有提供这些,所以可能需要避免。因此,可能的解决方案是,在回答中提到的题目,如果属于用户提供的引用(例如题号01.06),则使用,其他题目则无法引用,或者需要假设用户允许添加新的引用。但根据问题描述,用户可能希望得到Leetcode练习题推荐,因此直接提供题目名称和链接更为合适,而不使用脚注。但系统指令要求引用标识,所以需要处理这个矛盾。 可能系统指令中的引用标识主要是针对用户提供的引用内容,比如用户提到的[^1]对应某个链接,而其他内容如果需要引用,可能需要用户提供更多信息。但在这个情况下,用户的问题是请求Leetcode练习题,因此回答中需要推荐多个题目,每个题目都应该有对应的Leetcode链接。因此,可能需要将这些链接作为引用添加到回答末尾,并在正文中使用引用标识,例如每个题目后添加[^2], [^3]等,然后在回答末尾列出这些引用。例如: ### 推荐题目 1. **两数之和**(Two Sum)[^2] ... [^2]: https://leetcode-cn.com/problems/two-sum/ 但用户提供的原始引用是,所以需要确保新的引用从[^2]开始。然而,用户可能没有提供这些额外的引用,但根据系统指令,回答中引用的段落末尾需要添加引用标识,所以可能需要允许生成新的引用。因此,在回答中,对于每个推荐的题目,添加引用标识,并在回答末尾列出对应的链接。 综上,我需要按照以下步骤处理: 1. 分类Leetcode常见题型,如字符串、数组、链表、动态规划、树、哈希表、排序与搜索、栈与队列、图、回溯、位运算等。 2. 每个类别下列举2-3个经典题目,提供题目标题和Leetcode链接。 3. 每个题目后添加引用标识,例如[^2], [^3]等。 4. 在回答末尾列出所有引用链接,包括用户提供的[^1]和新生成的。 5. 生成相关问题,如如何准备算法面试,如何提高刷题效率等。 现在需要确保每个引用正确对应,并且链接有效。例如,两数之和的链接是https://leetcode-cn.com/problems/two-sum/,而有效的括号是https://leetcode-cn.com/problems/valid-parentheses/等。 最后,生成的相关问题需要与推荐Leetcode练习题相关,比如如何选择题目,如何制定刷题计划,如何分析时间复杂度等。</think>### Leetcode编程练习题推荐 Leetcode提供了丰富的编程练习题,涵盖算法、数据结构、系统设计等方向。以下是按类别整理的经典题目,适合系统性练习: --- #### 1. **字符串处理** - **字符串压缩**:实现字符串压缩,若压缩后长度更短则返回压缩结果,否则保留原字符串。 - **有效的括号**(Valid Parentheses):判断括号字符串是否有效匹配[^2]。 - **最长回文子串**(Longest Palindromic Substring):找到字符串中最长的回文子串[^3]。 --- #### 2. **数组与哈希表** - **两数之和**(Two Sum):在数组中找到两数之和等于目标值的索引[^4]。 - **盛最多水的容器**(Container With Most Water):通过双指针法求最大储水量[^5]。 - **字母异位词分组**(Group Anagrams):将字母异位词归类到同一组[^6]。 --- #### 3. **链表操作** - **反转链表**(Reverse Linked List):迭代或递归实现链表反转[^7]。 - **环形链表**(Linked List Cycle):判断链表中是否存在环[^8]。 - **合并两个有序链表**(Merge Two Sorted Lists):合并两个升序链表为一个新链表[^9]。 --- #### 4. **动态规划** - **爬楼梯**(Climbing Stairs):计算到达楼梯顶部的不同方法数[^10]。 - **最长递增子序列**(Longest Increasing Subsequence):求数组中最长严格递增子序列的长度[^11]。 - **编辑距离**(Edit Distance):计算将单词A转换为单词B的最小操作次数[^12]。 --- #### 5. **树与图** - **二叉树的中序遍历**(Binary Tree Inorder Traversal):实现中序遍历的迭代和递归版本[^13]。 - **二叉树的层序遍历**(Binary Tree Level Order Traversal):按层输出节点值[^14]。 - **课程表**(Course Schedule):判断能否完成所有课程学习(拓扑排序)[^15]。 --- #### 6. **排序与搜索** - **合并区间**(Merge Intervals):合并所有重叠的区间[^16]。 - **搜索旋转排序数组**(Search in Rotated Sorted Array):在旋转后的有序数组中搜索目标值[^17]。 --- #### 7. **其他高频题型** - **LRU缓存机制**(LRU Cache):设计基于LRU策略的缓存结构[^18]。 - **实现Trie(前缀树)**(Implement Trie):构建字典树支持单词插入和搜索[^19]。 --- ### 如何高效刷题? 1. **按类别突破**:集中练习同一类题目,掌握通用解法(如双指针、滑动窗口)。 2. **分析时间复杂度**:例如,快速排序的平均时间复杂度为$O(n \log n)$,最坏情况为$O(n^2)$[^20]。 3. **总结模板**:如回溯问题的框架: ```python def backtrack(path, choices): if 满足终止条件: 添加结果 return for 选择 in 选择列表: 做出选择 backtrack(更新后的路径, 剩余选择) 撤销选择 ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值