diff --git "a/topic04/submit/LC1080_\351\273\204\346\263\275\351\272\237.cpp" "b/topic04/submit/LC1080_\351\273\204\346\263\275\351\272\237.cpp" new file mode 100644 index 0000000000000000000000000000000000000000..64349032db4dc6faece28c3b7bd7df4ec555387c --- /dev/null +++ "b/topic04/submit/LC1080_\351\273\204\346\263\275\351\272\237.cpp" @@ -0,0 +1,100 @@ +#include +using namespace std; + +// 【题目】力扣1080. 根到叶路径上的不足节点 +// 【难度】中等 +// 【提交】2025.10.16 https://leetcode.cn/problems/insufficient-nodes-in-root-to-leaf-paths/submissions/671190355/ +// 【标签】树;深度优先搜索;二叉树 + +/** + * 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: + TreeNode* sufficientSubset(TreeNode* root, int limit) { + if(root->left == nullptr && root->right == nullptr) { + if(root->val < limit) return nullptr; + else return root; + } + if(root->left != nullptr) { + root->left = sufficientSubset(root->left, limit - root->val); + } + if(root->right != nullptr) { + root->right = sufficientSubset(root->right, limit - root->val); + } + if(root->left == nullptr && root->right == nullptr) return nullptr; + else return root; + } +}; + +/** + * @brief 学习总结: + * 一、题意与模型 + * 给定二叉树的根节点root和整数limit,删除所有不足节点。 + * 不足节点定义:如果从根节点到该节点的每条路径的和都严格小于limit,则该节点为不足节点。 + * 模型:树形结构+深度优先搜索,通过后序遍历判断和删除不足节点。 + * + * 二、标准解法状态设计 + * 1. 使用深度优先搜索遍历二叉树。 + * 2. 对于叶子节点,直接判断节点值是否小于limit。 + * 3. 对于非叶子节点,递归处理左右子树,并将limit减去当前节点值。 + * 4. 如果处理后的左右子树都为空,说明所有路径都不满足条件,删除当前节点。 + * + * 三、你的实现思路 + * 使用递归DFS,在遍历过程中判断节点是否为不足节点。 + * 通过后序遍历,先处理子节点再处理父节点,确保正确删除不足节点。 + * + * 四、逐行注释(带细节提醒) + * if(root->left == nullptr && root->right == nullptr) { // 叶子节点处理 + * if(root->val < limit) return nullptr; // 路径和不足,删除节点 + * else return root; // 路径和足够,保留节点 + * } + * + * if(root->left != nullptr) { // 递归处理左子树 + * root->left = sufficientSubset(root->left, limit - root->val); + * } + * + * if(root->right != nullptr) { // 递归处理右子树 + * root->right = sufficientSubset(root->right, limit - root->val); + * } + * + * if(root->left == nullptr && root->right == nullptr) return nullptr; // 左右子树都被删除,删除当前节点 + * else return root; // 至少有一个子树存在,保留当前节点 + * + * 五、正确性证明 + * 算法通过后序遍历遍历整棵树: + * 1. 对于叶子节点:直接判断从根到该叶子的路径和是否小于limit + * 2. 对于非叶子节点:只有当所有子路径(通过左右子树)都不满足条件时,才删除当前节点 + * 3. 通过递归将limit减去当前节点值,确保正确计算剩余需要的路径和 + * 由于DFS会访问所有节点,且每个节点都进行了正确的判断,因此算法正确。 + * + * 六、复杂度 + * 时间:O(n),每个节点只被访问一次。 + * 空间:O(h),递归栈深度为树的高度。 + * + * 七、优缺点分析 + * 优点: + * - 算法高效,只需一次DFS遍历; + * - 代码简洁,逻辑清晰; + * - 准确判断和删除不足节点。 + * 缺点: + * - 递归深度可能受树高度限制; + * - 修改了原始树结构。 + * + * 八、改进建议 + * 1. 可以添加输入验证:if (root == nullptr) return nullptr; + * 2. 可以使用迭代DFS避免递归深度问题; + * 3. 对于教学场景,可以添加注释解释为什么需要后序遍历。 + * + * 九、一句话总结 + * 通过深度优先搜索和后序遍历判断路径和,你的实现准确地删除了所有不足节点, + * 展现了树形结构问题中递归思想的精妙应用。 + */ \ No newline at end of file diff --git "a/topic04/submit/LC1443_\351\273\204\346\263\275\351\272\237.cpp" "b/topic04/submit/LC1443_\351\273\204\346\263\275\351\272\237.cpp" new file mode 100644 index 0000000000000000000000000000000000000000..927abec330ac056ffc08bcff77187e6d824d26da --- /dev/null +++ "b/topic04/submit/LC1443_\351\273\204\346\263\275\351\272\237.cpp" @@ -0,0 +1,102 @@ +#include +using namespace std; + +// 【题目】力扣1443. 收集树上所有苹果的最少时间 +// 【难度】中等 +// 【提交】2025.10.16 https://leetcode.cn/problems/minimum-time-to-collect-all-apples-in-a-tree/submissions/671180193/ +// 【标签】树;深度优先搜索 +class Solution { +public: + int minTime(int n, vector>& edges, vector& hasApple) { + vector> g(n); + for(auto& e : edges) { + g[e[0]].push_back(e[1]); + g[e[1]].push_back(e[0]); + } + return dfs(g, hasApple, 0, -1); + } + int dfs(vector>& g, vector& hasApple, int cur, int fa) { + int res = 0; + for(auto& child : g[cur]) { + if(child == fa) continue; + int child_time = dfs(g, hasApple, child, cur); + if(child_time > 0 || hasApple[child]) { + res += child_time + 2; + } + } + return res; + } +}; + +/** + * @brief 学习总结: + * 一、题意与模型 + * 给定一棵由n个节点组成的树,以及一个布尔数组hasApple表示哪些节点有苹果。 + * 从节点0出发,收集所有苹果并返回起点,每条边需要花费1单位时间。 + * 求收集所有苹果所需的最少时间。 + * 模型:树形结构+深度优先搜索,通过后序遍历计算收集苹果的总时间。 + * + * 二、标准解法状态设计 + * 1. 根据边列表构建树的邻接表表示。 + * 2. 使用深度优先搜索遍历树结构。 + * 3. 对于每个节点,递归计算所有子树的收集时间。 + * 4. 如果子树有苹果或子树收集时间大于0,则需要访问该子树,并加上往返时间2。 + * 5. 返回从根节点开始的总收集时间。 + * + * 三、你的实现思路 + * 使用DFS遍历树,在递归过程中计算收集每个子树中苹果所需的时间。 + * 通过判断子树是否有苹果或子树收集时间是否大于0,决定是否需要访问该子树。 + * + * 四、逐行注释(带细节提醒) + * vector> g(n); // 构建邻接表 + * for(auto& e : edges) { // 构建无向图 + * g[e[0]].push_back(e[1]); + * g[e[1]].push_back(e[0]); + * } + * + * int dfs(vector>& g, vector& hasApple, int cur, int fa) { + * int res = 0; // 初始化当前子树总时间 + * + * for(auto& child : g[cur]) { // 遍历邻居 + * if(child == fa) continue; // 跳过父节点 + * + * int child_time = dfs(g, hasApple, child, cur); // 递归计算子树时间 + * + * // 如果子树需要访问(有苹果或子树时间>0) + * if(child_time > 0 || hasApple[child]) { + * res += child_time + 2; // 加上子树时间和往返时间 + * } + * } + * return res; // 返回当前子树总时间 + * } + * + * 五、正确性证明 + * 算法通过DFS遍历整棵树,对于每个节点: + * 1. 计算所有子树的收集时间 + * 2. 如果子树有苹果或子树收集时间大于0,说明需要访问该子树 + * 3. 访问子树需要往返时间2(去1回1) + * 4. 最终得到从根节点出发收集所有苹果的总时间 + * 由于DFS会访问所有节点,且每个节点都正确计算了子树的访问时间,因此算法正确。 + * + * 六、复杂度 + * 时间:O(n),每个节点只被访问一次。 + * 空间:O(n),需要邻接表存储树结构,递归栈深度为树的高度。 + * + * 七、优缺点分析 + * 优点: + * - 算法高效,只需一次DFS遍历; + * - 代码简洁,逻辑清晰; + * - 准确计算了往返时间,避免了重复计算。 + * 缺点: + * - 递归深度可能受树高度限制; + * - 对于大规模数据可能需要迭代DFS。 + * + * 八、改进建议 + * 1. 可以添加输入验证:if (n == 0) return 0; + * 2. 可以使用迭代DFS(栈)避免递归深度问题; + * 3. 对于教学场景,可以添加注释解释为什么往返时间是2个单位。 + * + * 九、一句话总结 + * 通过深度优先搜索计算子树收集时间并累加往返时间,你的实现高效地解决了收集苹果的最少时间问题, + * 展现了树形结构问题中递归思想的实用价值。 + */ \ No newline at end of file diff --git "a/topic04/submit/LC3249_\351\273\204\346\263\275\351\272\237.cpp" "b/topic04/submit/LC3249_\351\273\204\346\263\275\351\272\237.cpp" new file mode 100644 index 0000000000000000000000000000000000000000..9eda1111a66688f5e89ea183275934a5d70e3cde --- /dev/null +++ "b/topic04/submit/LC3249_\351\273\204\346\263\275\351\272\237.cpp" @@ -0,0 +1,118 @@ +#include +using namespace std; + +// 【题目】力扣3249. 统计好节点的数目 +// 【难度】中等 +// 【提交】2025.10.16 https://leetcode.cn/problems/count-the-number-of-good-nodes/submissions/671171480/ +// 【标签】树;深度优先搜索;图 +class Solution_LC3249 { +public: + int countGoodNodes(vector>& edges) { + int res = 0; + vector> g(edges.size() + 1); + for(auto& e : edges) { + g[e[0]].push_back(e[1]); + g[e[1]].push_back(e[0]); + } + function dfs = [&](int n, int fa) -> int { + bool valid = true; + int total_size = 0; + int subtree_size = 0; + for(auto& child : g[n]) { + if(child != fa) { + int size = dfs(child, n); + if(subtree_size == 0) { + subtree_size = size; + } else if(size != subtree_size) { + valid = false; + } + total_size += size; + } + } + if(valid) res++; + return total_size + 1; + }; + dfs(0, -1); + return res; + } +}; + +/** + * @brief 学习总结: + * 一、题意与模型 + * 给定一个无向树的边列表,统计树中"好节点"的数量。 + * 好节点定义:如果删除该节点后,剩下的所有连通分量的大小都相同,则该节点为好节点。 + * 模型:树形结构+深度优先搜索,通过计算子树大小来判断节点是否满足条件。 + * + * 二、标准解法状态设计 + * 1. 根据边列表构建树的邻接表表示。 + * 2. 使用深度优先搜索遍历树结构。 + * 3. 对于每个节点,递归计算所有子树的尺寸。 + * 4. 检查所有子树的尺寸是否相同,如果相同则标记为好节点。 + * 5. 返回当前子树的总大小(包括当前节点)。 + * + * 三、你的实现思路 + * 使用DFS遍历树,在递归过程中计算每个节点的子树大小。 + * 通过比较所有子树的尺寸是否相同来判断节点是否为好节点。 + * + * 四、逐行注释(带细节提醒) + * vector> g(edges.size() + 1); // 构建邻接表 + * for(auto& e : edges) { // 构建无向图 + * g[e[0]].push_back(e[1]); + * g[e[1]].push_back(e[0]); + * } + * + * function dfs = [&](int n, int fa) -> int { + * bool valid = true; // 假设当前节点是好节点 + * int total_size = 0; // 所有子树总大小 + * int subtree_size = 0; // 用于比较的子树基准大小 + * + * for(auto& child : g[n]) { // 遍历邻居 + * if(child != fa) { // 排除父节点 + * int size = dfs(child, n); // 递归计算子树大小 + * if(subtree_size == 0) { // 第一个子树 + * subtree_size = size; // 设置基准大小 + * } else if(size != subtree_size) { // 子树大小不一致 + * valid = false; // 不是好节点 + * } + * total_size += size; // 累加子树大小 + * } + * } + * + * if(valid) res++; // 如果是好节点,增加计数 + * return total_size + 1; // 返回当前子树总大小(包括当前节点) + * }; + * + * dfs(0, -1); // 从根节点开始DFS + * return res; // 返回好节点总数 + * + * 五、正确性证明 + * 算法通过DFS遍历整棵树,对于每个节点: + * 1. 计算所有子树的尺寸 + * 2. 检查所有子树尺寸是否相同 + * 3. 如果相同,则该节点删除后剩下的连通分量大小相同,符合好节点定义 + * 由于DFS会访问所有节点,且每个节点都进行了正确的判断,因此算法正确。 + * + * 六、复杂度 + * 时间:O(n),每个节点只被访问一次。 + * 空间:O(n),需要邻接表存储树结构,递归栈深度为树的高度。 + * + * 七、优缺点分析 + * 优点: + * - 算法高效,只需一次DFS遍历; + * - 代码简洁,逻辑清晰; + * - 使用lambda表达式使代码更紧凑。 + * 缺点: + * - 递归深度可能受树高度限制; + * - 对于根节点的特殊情况可能需要额外考虑。 + * + * 八、改进建议 + * 1. 可以添加输入验证:if (edges.empty()) return 1; // 只有根节点 + * 2. 可以考虑使用迭代DFS避免递归深度问题; + * 3. 对于根节点,删除后剩下的连通分量是各个子树,算法已正确处理; + * 4. 对于叶子节点,没有子树,自动满足条件(valid保持为true)。 + * + * 九、一句话总结 + * 通过深度优先搜索计算子树大小并比较一致性,你的实现高效地统计了好节点数量, + * 展现了树形结构问题中递归思想的巧妙应用。 + */ \ No newline at end of file