LeetCode 865. Smallest Subtree with all the Deepest Nodes【树,DFS,BFS,哈希表】1534

本文属于「征服LeetCode」系列文章之一,这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁,本系列将至少持续到刷完所有无锁题之日为止;由于LeetCode还在不断地创建新题,本系列的终止日期可能是永远。在这一系列刷题文章中,我不仅会讲解多种解题思路及其优化,还会用多种编程语言实现题解,涉及到通用解法时更将归纳总结出相应的算法模板。

为了方便在PC上运行调试、分享代码文件,我还建立了相关的仓库:https://github.com/memcpy0/LeetCode-Conquest。在这一仓库中,你不仅可以看到LeetCode原题链接、题解代码、题解文章链接、同类题目归纳、通用解法总结等,还可以看到原题出现频率和相关企业等重要信息。如果有其他优选题解,还可以一同分享给他人。

由于本系列文章的内容随时可能发生更新变动,欢迎关注和收藏征服LeetCode系列文章目录一文以作备忘。

给定一个根为 root 的二叉树,每个节点的深度是 该节点到根的最短距离

返回包含原始树中所有 最深节点最小子树

如果一个节点在 整个树 的任意节点之间具有最大的深度,则该节点是 最深的

一个节点的 子树 是该节点加上它的所有后代的集合。

示例 1:

输入:root = [3,5,1,6,2,0,8,null,null,7,4]
输出:[2,7,4]
解释:
我们返回值为 2 的节点,在图中用黄色标记。
在图中用蓝色标记的是树的最深的节点。
注意,节点 532 包含树中最深的节点,但节点 2 的子树最小,因此我们返回它。

示例 2:

输入:root = [1]
输出:[1]
解释:根节点是树中最深的节点。

示例 3:

输入:root = [0,1,3,null,2]
输出:[2]
解释:树中最深的节点为 2 ,有效子树为节点 210 的子树,但节点 2 的子树最小。

提示:

  • 树中节点的数量在 [1, 500] 范围内。
  • 0 <= Node.val <= 500
  • 每个节点的值都是 独一无二 的。

注意: 本题与力扣 1123 重复:https://leetcode-cn.com/problems/lowest-common-ancestor-of-deepest-leaves


解法1 递归


看上图(示例 1),这棵树的节点 3 , 5 , 2 3,5,2 3,5,2 都是最深叶节点 7 , 4 7,4 7,4 的公共祖先,但只有节点 2 2 2 是最近的公共祖先。

如果我们要找的节点只在左子树中,那么最近公共祖先也必然只在左子树中。对于本题,如果左子树的最大深度比右子树的大,那么最深叶结点就只在左子树中,所以最近公共祖先也只在左子树中。反过来说,如果右子树的最大深度大于左子树,那么最深叶结点就只在右子树中,所以最近公共祖先也只在右子树中。

如果左右子树的最大深度一样呢?当前节点一定是最近公共祖先吗?不一定。比如节点 1 1 1 的左右子树最深叶节点 0 , 8 0,8 0,8 的深度都是 2 2 2 ,但该深度并不是全局最大深度,所以节点 1 1 1 并不能是答案。

根据以上讨论,正确做法如下:

  • 递归这棵二叉树,同时维护全局最大深度 maxDepth \textit{maxDepth} maxDepth
  • 在「」的时候往下传 d e p t h depth depth ,用来表示当前节点的深度
  • 在「」的时候往上传当前子树最深叶节点的深度
  • 设左子树最深叶节点的深度为 leftMaxDepth \textit{leftMaxDepth} leftMaxDepth ,右子树最深叶节点的深度为 rightMaxDepth \textit{rightMaxDepth} rightMaxDepth 。如果 leftMaxDepth = rightMaxDepth = maxDepth \textit{leftMaxDepth}=\textit{rightMaxDepth}=\textit{maxDepth} leftMaxDepth=rightMaxDepth=maxDepth ,那么更新答案为当前节点。注意这并不代表我们找到了答案,如果后面发现了更深的叶节点,那么答案还会更新。
class Solution {
public:TreeNode *subtreeWithAllDeepest(TreeNode *root) {TreeNode *ans = nullptr;int max_depth = -1; // 全局最大深度function<int(TreeNode*, int)> dfs = [&](TreeNode *node, int depth) {if (node == nullptr) {max_depth = max(max_depth, depth); // 维护全局最大深度return depth;}int left_max_depth = dfs(node->left, depth + 1); // 获取左子树最深叶节点的深度int right_max_depth = dfs(node->right, depth + 1); // 获取右子树最深叶节点的深度if (left_max_depth == right_max_depth && left_max_depth == max_depth)ans = node;return max(left_max_depth, right_max_depth); // 当前子树最深叶节点的深度};dfs(root, 0);return ans;}
};

复杂度分析:

  • 时间复杂度: O ( n ) \mathcal{O}(n) O(n) 。每个节点都会恰好访问一次。
  • 空间复杂度: O ( n ) \mathcal{O}(n) O(n) 。最坏情况下,二叉树是一条链,递归需要 O(n)\mathcal{O}(n)O(n) 的栈空间。

解法2 自底向上

也可以不用全局变量,而是把每棵子树都看成是一个「子问题」,即对于每棵子树,我们需要知道:

  • 这棵子树最深叶结点的深度。这里是指叶子在这棵子树内的深度,而不是在整棵二叉树的视角下的深度。相当于这棵子树的高度
  • 这棵子树的最深叶结点的最近公共祖先 lca \textit{lca} lca

分类讨论:

  • 设子树的根节点为 n o d e node node n o d e node node 的左子树的高度为 leftHeight \textit{leftHeight} leftHeight n o d e node node 的右子树的高度为 rightHeight \textit{rightHeight} rightHeight
  • 如果 l e f t H e i g h t > r i g h t H e i g h t leftHeight>rightHeight leftHeight>rightHeight ,那么子树的高度为 leftHeight + 1 \textit{leftHeight} + 1 leftHeight+1 lca \textit{lca} lca 是左子树的 lca \textit{lca} lca
  • 如果 leftHeight < rightHeight \textit{leftHeight} < \textit{rightHeight} leftHeight<rightHeight ,那么子树的高度为 r i g h t H e i g h t + 1 rightHeight+1 rightHeight+1 l c a lca lca 是右子树的 l c a lca lca
  • 如果 leftHeight = rightHeight \textit{leftHeight} = \textit{rightHeight} leftHeight=rightHeight ,那么子树的高度为 leftHeight + 1 \textit{leftHeight} + 1 leftHeight+1 l c a lca lca 就是 n o d e node node 。反证法:如果 l c a lca lca 在左子树中,那么 l c a lca lca 不是右子树的最深叶结点的祖先,这不对;如果 l c a lca lca 在右子树中,那么 l c a lca lca 不是左子树的最深叶结点的祖先,这也不对;如果 l c a lca lca n o d e node node 的上面,那就不符合「最近」的要求。所以 l c a lca lca 只能是 n o d e node node
class Solution {pair<int, TreeNode*> dfs(TreeNode *node) {if (node == nullptr)return {0, nullptr};auto [left_height, left_lca] = dfs(node->left);auto [right_height, right_lca] = dfs(node->right);if (left_height > right_height) // 左子树更高return {left_height + 1, left_lca};if (left_height < right_height) // 右子树更高return {right_height + 1, right_lca};return {left_height + 1, node}; // 一样高}public:TreeNode *subtreeWithAllDeepest(TreeNode *root) {return dfs(root).second;}
};

复杂度分析:

  • 时间复杂度: O ( n ) \mathcal{O}(n) O(n) 。每个节点都会恰好访问一次。
  • 空间复杂度: O ( n ) \mathcal{O}(n) O(n) 。最坏情况下,二叉树是一条链,递归需要 O ( n ) \mathcal{O}(n) O(n) 的栈空间。

更简洁的写法是:

class Solution {
public:int depth[1010];TreeNode* subtreeWithAllDeepest(TreeNode* root) {if (root == nullptr) return nullptr;TreeNode* left = root->left, *right = root->right;TreeNode* lcaLeft = subtreeWithAllDeepest(root->left), *lcaRight = subtreeWithAllDeepest(root->right);int dl = left ? depth[left->val] : 0, dr = right ? depth[right->val] : 0;depth[root->val] = max(dl, dr) + 1;if (dl > dr) return lcaLeft;if (dr > dl) return lcaRight;return root;}
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/102083.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

聚鑫数藏平台——引领数字资产管理新风向

随着数字经济的飞速发展&#xff0c;新金融生态应运而生。区块链技术的崭新突破&#xff0c;使数字资产的重要性日益凸显&#xff0c;为投资者带来了前所未有的机遇和挑战。在此背景下&#xff0c;聚鑫数藏平台横空出世&#xff0c;引领着数字资产管理的新风向。 聚鑫数藏平台&…

UDP聊天室

1.头文件 /* * 文件名称&#xff1a;UDP.h * 创 建 者&#xff1a;crx * 创建日期&#xff1a;2023年09月3日 * 描 述&#xff1a; */ #ifndef _UDP_H #define _UDP_H#include <stdio.h> #include <sys/types.h> /* See NOTES */ #includ…

TypeScript断言

什么是断言&#xff1f; 一个编译时语法&#xff0c;用于告诉编译器用户比编译器更加确定变量的类型&#xff0c;进而解除编译错误&#xff0c;类型断言有点类似于其他语言的类型转换&#xff0c;但它没有运行时的影响&#xff0c;只是在编译阶段起作用。所以&#xff0c;即使通…

QT Day5思维导图

聊天室代码&#xff1a; #include "cli.h" #include "ui_cli.h"Cli::Cli(QWidget *parent): QWidget(parent), ui(new Ui::Cli) {ui->setupUi(this);socket new QTcpSocket(this);connect(socket,&QTcpSocket::connected,this,&Cli::connect_…

2023年全国大学生数学建模B题

多波束测线问题 1.问题提出 单波束测深是利用声波在水中的传播特性来测量水体深度的技术。声波在均匀介质中作匀 速直线传播&#xff0c;在不同界面上产生反射&#xff0c;利用这一原理&#xff0c;从测量船换能器垂直向海底发射声波信号&#xff0c;并记录从声波发射到信号接…

C#开发的OpenRA游戏之系统参数选项按钮

C#开发的OpenRA游戏之系统参数选项按钮 前面分析了信标按钮,从图上可以看到,靠右边的按钮,就是系统参数选项按钮: 这个按钮与前面三个按钮是不一样的,虽然它们在排列位置上是放在一起,但是处理的方法方式是不一样的,因为这个选项按钮,并不需要发命令给服务器,再返回来…

《Effective STL》读书笔记(二):vector和string

vector 和 string 优先于动态分配数组 当使用new动态分配内存时&#xff0c;我们需要关注以下内容 必须保证动态分配的内存会被delete&#xff0c;否则会造成资源泄露必须确保使用了正确的delete形式。如果分配了单个对象&#xff0c;则必须使用delete&#xff1b;如果分配了…

Python实现猎人猎物优化算法(HPO)优化卷积神经网络分类模型(CNN分类算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 猎人猎物优化搜索算法(Hunter–prey optimizer, HPO)是由Naruei& Keynia于2022年提出的一种最新的…

浅谈能源汽车下乡充电桩建设优化建议及解决方案

1.趋势分析 新能源汽车下乡已经成为提振汽车市场表现、推动汽车行业发展的重要措施。国家发改委日前也提出&#xff0c;汽车消费是支撑消费的“大头”&#xff0c;将加快推进充电桩和城市停车设施建设&#xff0c;大力推动新能源汽车下乡&#xff0c;鼓励汽车企业开发更适宜县…

将目标检测项目移植到linux上出现OSERROR

在windows上运行项目正常&#xff0c;但是在centos9上运行出现找到资源&#xff0c;第一次遇到这个问题&#xff0c;通过代码回找&#xff0c;一步一步发现&#xff0c;读取数据没问题&#xff0c;但是在预测的时候无法读取&#xff0c;查到的资料 说明显示字体问题&#xff0c…

十八、MySQL添加外键?

1、外键 外键是用来让两张表的数据之间建立联系&#xff0c;从而保证数据的一致性和完整性。 注意&#xff0c;父表被关联的字段类型&#xff0c;必须和子表被关联的字段类型一致。 2、实际操作 &#xff08;1&#xff09;初始化两张表格&#xff1a; 子表&#xff1a; 父…

【Apollo学习笔记】——规划模块TASK之PIECEWISE_JERK_NONLINEAR_SPEED_OPTIMIZER(二)

文章目录 TASK系列解析文章OptimizeByNLP1.get_nlp_info()定义问题规模2.get_bounds_info()定义约束边界约束3.get_starting_point()定义初值4.eval_f()求解目标函数5.eval_grad_f()求解梯度6.eval_g()求解约束函数7.eval_jac_g()求解约束雅可比矩阵8.eval_h()求解黑塞矩阵9. f…