Leetcode-Course Schedule && Course Schedule II

本文解析了课程安排问题的算法实现,通过拓扑排序解决课程先修顺序问题,探讨了使用邻接链表存储图结构的方法,并实现了判断是否可以完成所有课程及输出合理课程顺序的算法。

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

Problem

Course Schedule

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:
1.The input prerequisites is a graph represented by a list of edges, not adjacency matrices. Read more about how a graph is represented.
2.You may assume that there are no duplicate edges in the input prerequisites.

具体详见Leetcode

Course Schedule II

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, return the ordering of courses you should take to finish all courses.

There may be multiple correct orders, you just need to return one of them. If it is impossible to finish all courses, return an empty array.

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 the correct course order is [0,1]

4, [[1,0],[2,0],[3,1],[3,2]]
There are a total of 4 courses to take. To take course 3 you should have finished both courses 1 and 2. Both courses 1 and 2 should be taken after you finished course 0. So one correct course order is [0,1,2,3]. Another correct ordering is[0,2,1,3].

Note:
1.The input prerequisites is a graph represented by a list of edges, not adjacency matrices. Read more about how a graph is represented.
2.You may assume that there are no duplicate edges in the input prerequisites.

具体详见Leetcode

Analysis

Thoughts

两道题的本质是一样的,所以做完第二题,第一题的答案就出来了。所以直接思考第二题。

一开始的想法就是这是一个图的问题,而且是一个有向图的问题,由于每门课可能都有一个前驱的课,所以就形成了一种优先关系。把问题拨开来看就是一个拓扑排序的问题。
首先知道有n个点,然后边就由vector< int >prerequisites给出。需要思考的第一个问题是,如何存储这个图。有两种方式,第一种是邻接矩阵,第二种是邻接链表。因为没有说明数据量的大小,假设数据量很大的情况下(顶点很多),用邻接矩阵可能会超出内存空间,所以,考虑用邻接链表。
解决了存储问题,该题的思路就是如何进行拓扑排序。由于这是抽象成图的题,所以从图的角度来看,我们可以抽象成图的入度问题。用一个队列把入度为0的点存起来,然后每次访问完,把它能到的点的入度减一,然后发现更新后点的入度变成0后加入队列。
注意的问题还有,注意有没有环,如果有环就不存在解。

Complexity

时间复杂度: O(VE)
空间复杂度: O(V+E)

Code

Course Schedule

struct Node {
    int num;
    Node* next;
    Node(int n) {
        num = n;
        next = NULL;
    }
};
class Solution {
public:
    bool canFinish(int numCourses, vector<pair<int, int>>& prerequisites) {
        vector<pair<int, int>> edges(prerequisites);
        int n = numCourses;
        vector<int> ins;
        vector<Node*> outDegree;
        std::vector<int> v;
        for (int i = 0; i < n; i++) {
            ins.push_back(0);

            outDegree.push_back(NULL);
        }
        for (int i = 0; i < edges.size(); i++) {
            int in = edges[i].second;
            int out = edges[i].first;
            ins[in]++;
            Node* temp = outDegree[out];
            if (temp != NULL) {
                while (temp->next != NULL) {
                    temp = temp->next;
                }
                temp->next = new Node(in);
            }
            else {
                outDegree[out] = new Node(in);
            }
        }

        int j = 0;
        queue<int> q;
        for (j = 0; j < n; j++) {
            if (ins[j] == 0) {
                q.push(j);
            }
        }

        if (q.size() == 0) return false;



        while (v.size() < n) {

            int nxt = q.front();
            q.pop();
            v.push_back(nxt);

            Node* tmp = outDegree[nxt];
            while (tmp != NULL) {
                ins[tmp->num]--;
                if (ins[tmp->num] == 0) q.push(tmp->num);
                tmp = tmp->next;
            }
            if (q.size() == 0 && v.size() < n) return false;

        }

        return true;
    }
};

Course Schedule II

struct Node {
    int num;
    Node* next;
    Node(int n) {
        num = n;
        next = NULL;
    }
};
class Solution {
public:
    vector<int> findOrder(int numCourses, vector<pair<int, int>>& prerequisites) {
        int n = numCourses;
        vector<pair<int, int>> edges(prerequisites);
        vector<int> ins;
        vector<Node*> outDegree;
        std::vector<int> v;
        for (int i = 0; i < n; i++) {
            ins.push_back(0);

            outDegree.push_back(NULL);
        }
        for (int i = 0; i < edges.size(); i++) {
            int out = edges[i].second;
            int in = edges[i].first;
            ins[in]++;
            Node* temp = outDegree[out];
            if (temp != NULL) {
                while (temp->next != NULL) {
                    temp = temp->next;
                }
                temp->next = new Node(in);
            }
            else {
                outDegree[out] = new Node(in);
            }
        }

        int j = 0;
        queue<int> q;
        for (j = 0; j < n; j++) {
            if (ins[j] == 0) {
                q.push(j);
            }
        }
        vector<int> empty;
        if (q.size() == 0) return empty;




        while (v.size() < n) {

            int nxt = q.front();
            q.pop();
            v.push_back(nxt);

            Node* tmp = outDegree[nxt];
            while (tmp != NULL) {
                ins[tmp->num]--;
                if (ins[tmp->num] == 0) q.push(tmp->num);
                tmp = tmp->next;
            }
            if (q.size() == 0 && v.size() < n) return empty;

        }

        return v;
    }
};
<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、付费专栏及课程。

余额充值