leetcode----207. Course Schedule

本文深入探讨了课程调度问题,利用有向图和拓扑排序原理,设计了一种判断能否完成所有课程的学习路径算法。通过构建节点入度数组和边信息图,结合改进的寻找入度为0节点的方法,有效提升了算法效率。

链接:

https://leetcode.com/problems/course-schedule/

大意:

给定一个整数num为所需学习的课程数目,一个二维整数数组pre为修习课程的先决条件。若pre中某个一维数组为[0,1],则表示要学习0号课程,则必须先学习1号课程。规定:课程编号为0~num-1。现在要求判断是否有一种安排使得可以修习完所有课程。例子:

思路:

说的这么多,其实就是在一个有向图中判断是否存在拓扑序列。

通过num值,构造一个每个节点的入度数组degree,以及构造一个以每个节点为边的起点的边信息graph(其实就是构图)。

通过pre数组,对degree和graph进行初始化。

接下来就是模拟寻找拓扑序列了。

首先,找到图中剩余顶点中的一个入度为0的顶点,之后将以该顶点为起点的边所指向的节点的入度减1,同时需要将该节点置为已访问(不然下次找入度为0的顶点时又会找到它)。之后便是重复该过程。知道找到的顶点数(课程数)等于num(返回true)或者找不到入度为0的点了 (返回false)

代码:

class Solution {
    // 判断一个图是否可以找出一个拓扑序列
    public boolean canFinish(int num, int[][] pre) {
        int[] degree = new int[num]; // 记录每个节点的入度
        List<Integer>[] graph = new ArrayList[num]; // 记录各个边的关系
        for (int i = 0; i < num; i++) {
            graph[i] = new ArrayList<>();
        }
        for (int[] nums : pre) {
            degree[nums[0]]++;
            graph[nums[1]].add(nums[0]); // nums[1] -> nums[0]
        }
        int count = 0;
        boolean[] visited = new boolean[num]; // 记录节点是否已被访问
        while (count < num) {
            int curIdx = -1;
            // 尝试找到一个入度为0的点
            for (int i = 0; i < degree.length; i++) {
                if (!visited[i] && degree[i] == 0) {
                    curIdx = i;
                    break;
                }
            }
            // 没找到入度为0的点
            if (curIdx == -1)
                break;
            visited[curIdx] = true;
            for (int tail : graph[curIdx]) {
                degree[tail]--;
            }
            count++;
        }
        return count == num;
    }
}

结果:

结论:

效率有点低,需要改进。

改进:

猜测主要性能瓶颈是在while循环中尝试找到入度为0的点的for循环中,需要遍历一次degree数组。可以使用一个列表idxs首先存储一开始入度为0的点,然后依次取出list的点,判断该点指向的那些点入度减1后是否为0.若为0,则将指向的该点也加入idxs

当idxs为空时跳出循环,并判断count是否与num相等

class Solution {
    // 判断一个图是否可以找出一个拓扑序列
    public boolean canFinish(int num, int[][] pre) {
        int[] degree = new int[num]; // 记录每个节点的入度
        List<Integer>[] graph = new ArrayList[num]; // 记录各个边的关系
        for (int i = 0; i < num; i++) {
            graph[i] = new ArrayList<>();
        }
        for (int[] nums : pre) {
            degree[nums[0]]++;
            graph[nums[1]].add(nums[0]); // nums[1] -> nums[0]
        }
        int count = 0;
        ArrayList<Integer> idxs = new ArrayList<>(); // 存储所有当前入度为0的点的位置
        // 记录入度为0的点
        for (int i = 0; i < degree.length; i++) {
            if (degree[i] == 0) 
                idxs.add(i);
        }
        while (!idxs.isEmpty()) {
            int curIdx = idxs.remove(idxs.size() - 1);
            for (int tail : graph[curIdx]) {
                if (--degree[tail] == 0) // 又找到一个新的入度为0的点
                    idxs.add(tail);
            }
            count++;
        }
        return count == num;
    }
}

 

 

 

<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(更新后的路径, 剩余选择) 撤销选择 ``` ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值