diff --git "a/topic04/submit/LC0310_\351\273\204\346\263\275\351\272\237.cpp" "b/topic04/submit/LC0310_\351\273\204\346\263\275\351\272\237.cpp" new file mode 100644 index 0000000000000000000000000000000000000000..d539a05d440a3f0e1f2c00bb7b6bdff602810a04 --- /dev/null +++ "b/topic04/submit/LC0310_\351\273\204\346\263\275\351\272\237.cpp" @@ -0,0 +1,124 @@ +#include +using namespace std; + +// 【题目】力扣310. 最小高度树 +// 【难度】中等 +// 【提交】2025.10.14 https://leetcode.cn/problems/minimum-height-trees/submissions/670585271/ +// 【标签】图;拓扑排序;广度优先搜索 +class Solution { +public: + vector findMinHeightTrees(int n, vector>& edges) { + if(n == 1) return {0}; + vector d(n, 0); + vector> g(n); + for(auto& e : edges) { + g[e[0]].push_back(e[1]); + g[e[1]].push_back(e[0]); + d[e[0]]++; + d[e[1]]++; + } + queue q; + for(int i = 0; i < n; i++) { + if(d[i] == 1) q.push(i); + } + while(n > 2) { + int k = q.size(); + n -= k; + for(int i = 0; i < k; i++) { + int t = q.front(); + q.pop(); + for(auto& v : g[t]) { + d[v]--; + if(d[v] == 1) q.push(v); + } + } + } + vector res; + while(!q.empty()) { + res.push_back(q.front()); + q.pop(); + } + return res; + } +}; + +/** + * @brief 学习总结: + * 一、题意与模型 + * 给定一个包含n个节点的树(无向无环图),找到所有的根节点,使得以这些节点为根时树的高度最小。 + * 模型:拓扑排序+广度优先搜索,通过逐层删除叶子节点找到中心节点。 + * + * 二、标准解法状态设计 + * 1. 构建图的邻接表和度数数组。 + * 2. 将所有叶子节点(度数为1)加入队列。 + * 3. 逐层删除叶子节点,直到剩余节点数不超过2。 + * 4. 剩余的节点即为最小高度树的根节点。 + * + * 三、你的实现思路 + * 使用拓扑排序的思想,从外向内逐层删除叶子节点。 + * 最终剩下的1个或2个节点就是最小高度树的根节点。 + * + * 四、逐行注释(带细节提醒) + * if(n == 1) return {0}; // 特殊情况处理 + * + * vector d(n, 0); // 度数数组 + * vector> g(n); // 邻接表 + * + * for(auto& e : edges) { // 构建图 + * g[e[0]].push_back(e[1]); + * g[e[1]].push_back(e[0]); + * d[e[0]]++; // 更新度数 + * d[e[1]]++; + * } + * + * queue q; // 初始化队列 + * for(int i = 0; i < n; i++) { // 将所有叶子节点入队 + * if(d[i] == 1) q.push(i); + * } + * + * while(n > 2) { // 拓扑排序过程 + * int k = q.size(); // 当前层叶子节点数 + * n -= k; // 更新剩余节点数 + * + * for(int i = 0; i < k; i++) { // 处理当前层 + * int t = q.front(); q.pop(); + * for(auto& v : g[t]) { // 更新邻居 + * d[v]--; + * if(d[v] == 1) q.push(v); // 新叶子节点入队 + * } + * } + * } + * + * vector res; // 收集结果 + * while(!q.empty()) { + * res.push_back(q.front()); q.pop(); + * } + * return res; + * + * 五、正确性证明 + * 最小高度树的根节点一定是图中最"中心"的节点。 + * 通过逐层删除叶子节点,我们不断向中心收缩,最终剩下的节点就是最中心的节点。 + * 对于树状结构,最终会剩下1个或2个中心节点。 + * + * 六、复杂度 + * 时间:O(n),每个节点和边只被处理一次。 + * 空间:O(n),需要存储邻接表和度数数组。 + * + * 七、优缺点分析 + * 优点: + * - 算法高效,只需线性时间; + * - 思路巧妙,利用了拓扑排序的思想; + * - 代码实现简洁。 + * 缺点: + * - 算法理解起来有一定难度; + * - 需要额外的空间存储图结构。 + * + * 八、改进建议 + * 1. 可以添加输入验证:if (n == 0) return {}; + * 2. 使用更明确的变量名(如 degree 代替 d)来增强可读性; + * 3. 对于教学场景,可以添加注释解释为什么最终会剩下1个或2个节点。 + * + * 九、一句话总结 + * 通过拓扑排序逐层删除叶子节点,你的实现高效地找到了最小高度树的根节点, + * 展现了图论算法在解决树结构问题中的应用能力。 + */ \ No newline at end of file diff --git "a/topic04/submit/LC0687_\351\273\204\346\263\275\351\272\237.cpp" "b/topic04/submit/LC0687_\351\273\204\346\263\275\351\272\237.cpp" new file mode 100644 index 0000000000000000000000000000000000000000..2954fa9cb69a6ce238fee220bd42cedd05c544bf --- /dev/null +++ "b/topic04/submit/LC0687_\351\273\204\346\263\275\351\272\237.cpp" @@ -0,0 +1,126 @@ +#include +using namespace std; + +// 【题目】力扣687. 最长同值路径 +// 【难度】中等 +// 【提交】2025.10.14 https://leetcode.cn/problems/longest-univalue-path/submissions/670579530/ +// 【标签】树;深度优先搜索;二叉树 + +/** + * Definition for a binary tree node. + * struct TreeNode { + * int val; + * TreeNode *left; + * TreeNode *right; + * TreeNode() : val(0), left(nullptr), right(nullptr) {} + * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} + * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {} + * }; + */ +class Solution { +public: + int res; + int longestUnivaluePath(TreeNode* root) { + res = 0; + if(!root) return 0; + int ans = 0; + dfs(root, ans); + return res; + } + int dfs(TreeNode* root, int& ans) { + if(root == nullptr) return 0; + int l = dfs(root->left, ans); + int r = dfs(root->right, ans); + int l1 = 0, r1 = 0; + if(root->left && root->left->val == root->val) { + l1 += l + 1; + } + if(root->right && root->right->val == root->val) { + r1 += r + 1; + } + res = max(res, l1 + r1); + return max(l1, r1); + } +}; + +/** + * @brief 学习总结: + * 一、题意与模型 + * 给定一个二叉树的根节点root,找出最长路径的长度,这个路径中的每个节点具有相同值。 + * 这条路径可以经过也可以不经过根节点。两个节点之间的路径长度由它们之间的边数表示。 + * 模型:树形DP,通过深度优先搜索计算每个节点的最长同值路径。 + * + * 二、标准解法状态设计 + * 1. 使用深度优先搜索遍历二叉树。 + * 2. 对于每个节点,计算以该节点为端点的最长同值路径(单侧)。 + * 3. 同时计算以该节点为转折点的最长同值路径(两侧),并更新全局最大值。 + * 4. 返回全局最大值作为结果。 + * + * 三、你的实现思路 + * 使用递归深度优先搜索,在遍历过程中维护两个值: + * - 以当前节点为端点的最长同值路径(单侧) + * - 以当前节点为转折点的最长同值路径(两侧) + * 通过比较左右子树的同值路径,更新全局最大值。 + * + * 四、逐行注释(带细节提醒) + * int res; // 全局变量记录最长路径 + * + * int longestUnivaluePath(TreeNode* root) { + * res = 0; // 初始化 + * if(!root) return 0; // 空树处理 + * int ans = 0; // 参数(实际未使用) + * dfs(root, ans); // 执行DFS + * return res; // 返回结果 + * } + * + * int dfs(TreeNode* root, int& ans) { + * if(root == nullptr) return 0; // 递归终止条件 + * + * int l = dfs(root->left, ans); // 左子树最长同值路径 + * int r = dfs(root->right, ans); // 右子树最长同值路径 + * + * int l1 = 0, r1 = 0; // 当前节点的左右延伸长度 + * + * // 检查左子节点是否可以延伸 + * if(root->left && root->left->val == root->val) { + * l1 = l + 1; + * } + * + * // 检查右子节点是否可以延伸 + * if(root->right && root->right->val == root->val) { + * r1 = r + 1; + * } + * + * res = max(res, l1 + r1); // 更新全局最大值(两侧路径) + * return max(l1, r1); // 返回单侧最大值 + * } + * + * 五、正确性证明 + * 算法通过深度优先搜索遍历所有节点,对于每个节点: + * 1. 计算以该节点为端点的最长同值路径(只能选择左右子树中的一条路径) + * 2. 计算以该节点为转折点的最长同值路径(可以同时包含左右子树) + * 3. 全局变量res记录了遍历过程中遇到的最大路径长度 + * 由于遍历了所有节点,且每个节点都考虑了所有可能的最长路径情况,因此算法正确。 + * + * 六、复杂度 + * 时间:O(n),其中n是树中的节点数,每个节点只被访问一次。 + * 空间:O(h),其中h是树的高度,递归栈的深度。 + * + * 七、优缺点分析 + * 优点: + * - 算法高效,只需一次深度优先遍历; + * - 代码简洁,逻辑清晰; + * - 同时考虑了单侧路径和转折路径两种情况。 + * 缺点: + * - 使用了全局变量,可能不是纯函数; + * - 递归深度可能受树高度限制。 + * + * 八、改进建议 + * 1. 可以避免使用全局变量,通过引用参数传递结果; + * 2. 可以移除未使用的ans参数; + * 3. 对于教学场景,可以添加注释解释路径长度的计算方式。 + * + * 九、一句话总结 + * 通过深度优先搜索和树形动态规划,你的实现高效地解决了二叉树中最长同值路径问题, + * 展现了递归思想在树结构问题中的应用能力。 + */ \ No newline at end of file diff --git "a/topic04/submit/LC1376_\351\273\204\346\263\275\351\272\237.cpp" "b/topic04/submit/LC1376_\351\273\204\346\263\275\351\272\237.cpp" new file mode 100644 index 0000000000000000000000000000000000000000..2b76d9739365b6a01b00c7e0c4595c71d94d7c45 --- /dev/null +++ "b/topic04/submit/LC1376_\351\273\204\346\263\275\351\272\237.cpp" @@ -0,0 +1,92 @@ +#include +using namespace std; + +// 【题目】力扣1376. 通知所有员工所需的时间 +// 【难度】中等 +// 【提交】2025.10.14 https://leetcode.cn/problems/time-needed-to-inform-all-employees/submissions/670590019/ +// 【标签】树;深度优先搜索;广度优先搜索 +class Solution { +public: + int numOfMinutes(int n, int headID, vector& manager, vector& informTime) { + vector> g(n); + for (int i = 0; i < n; i++) { + if (manager[i] != -1) { + g[manager[i]].push_back(i); + } + } + function dfs = [&](int u) -> int { + int res = 0; + for (auto v : g[u]) { + res = max(res, dfs(v)); + } + return informTime[u] + res; + }; + return dfs(headID); + } +}; + +/** + * @brief 学习总结: + * 一、题意与模型 + * 公司有n名员工,每个员工有唯一ID(0到n-1),headID是总负责人ID。 + * manager数组表示每个员工的直接负责人,informTime数组表示每个员工通知下属所需时间。 + * 求从总负责人开始,通知到所有员工所需的最短时间。 + * 模型:树形结构+深度优先搜索,计算从根节点到所有叶子节点的最大路径和。 + * + * 二、标准解法状态设计 + * 1. 根据manager数组构建员工关系树(邻接表)。 + * 2. 使用深度优先搜索遍历树结构。 + * 3. 对于每个节点,计算通知所有下属所需的最大时间。 + * 4. 当前节点的总时间 = 通知下属的时间 + 下属中最大的通知时间。 + * + * 三、你的实现思路 + * 使用邻接表构建员工关系树,然后使用DFS递归计算从总负责人到所有员工的最大通知时间。 + * 使用lambda表达式定义DFS函数,代码简洁。 + * + * 四、逐行注释(带细节提醒) + * vector> g(n); // 构建邻接表 + * for (int i = 0; i < n; i++) { + * if (manager[i] != -1) { + * g[manager[i]].push_back(i); // 经理管理下属 + * } + * } + * + * function dfs = [&](int u) -> int { // DFS函数 + * int res = 0; // 记录最大下属通知时间 + * for (auto v : g[u]) { // 遍历所有下属 + * res = max(res, dfs(v)); // 递归计算并取最大值 + * } + * return informTime[u] + res; // 当前节点时间 = 通知时间 + 最大下属时间 + * }; + * + * return dfs(headID); // 从总负责人开始DFS + * + * 五、正确性证明 + * 算法通过DFS遍历整棵树,对于每个节点: + * 1. 计算所有下属中最大的通知时间 + * 2. 当前节点的总时间 = 自身通知时间 + 最大下属时间 + * 由于通知是并行进行的,所以总时间取决于最慢的那条路径。 + * 从根节点开始递归计算,最终得到的就是通知所有员工所需的最短时间。 + * + * 六、复杂度 + * 时间:O(n),每个节点只被访问一次。 + * 空间:O(n),需要邻接表存储树结构,递归栈深度为树的高度。 + * + * 七、优缺点分析 + * 优点: + * - 算法高效,只需一次DFS遍历; + * - 代码简洁,使用lambda表达式使代码更紧凑; + * - 逻辑清晰,易于理解。 + * 缺点: + * - 递归深度可能受树高度限制,对于极端不平衡的树可能栈溢出; + * - 使用递归有一定函数调用开销。 + * + * 八、改进建议 + * 1. 可以添加输入验证:if (n == 0) return 0; + * 2. 可以使用迭代DFS(栈)或BFS(队列)避免递归深度问题; + * 3. 对于教学场景,可以添加注释解释为什么取最大值(并行通知)。 + * + * 九、一句话总结 + * 通过深度优先搜索遍历员工关系树,你的实现高效地计算了通知所有员工所需的最短时间, + * 展现了树形结构问题中递归思想的优雅应用。 + */ \ No newline at end of file