LeetCode 1123. Lowest Common Ancestor of Deepest Leaves【树,DFS,BFS,哈希表】1607

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

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

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

给你一个有根节点 root 的二叉树,返回它 最深的叶节点的最近公共祖先

回想一下:

  • 叶节点 是二叉树中没有子节点的节点
  • 树的根节点的 深度0,如果某一节点的深度为 d,那它的子节点的深度就是 d+1
  • 如果我们假定 A 是一组节点 S最近公共祖先S 中的每个节点都在以 A 为根节点的子树中,且 A 的深度达到此条件下可能的最大值。

示例 1:

输入:root = [3,5,1,6,2,0,8,null,null,7,4]
输出:[2,7,4]
解释:我们返回值为 2 的节点,在图中用黄色标记。
在图中用蓝色标记的是树的最深的节点。
注意,节点 608 也是叶节点,但是它们的深度是 2 ,而节点 74 的深度是 3

示例 2:

输入:root = [1]
输出:[1]
解释:根节点是树中最深的节点,它是它本身的最近公共祖先。

示例 3:

输入:root = [0,1,3,null,2]
输出:[2]
解释:树中最深的叶节点是 2 ,最近公共祖先是它自己。

提示:

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

注意: 本题与力扣 865 重复:https://leetcode-cn.com/problems/smallest-subtree-with-all-the-deepest-nodes/


解法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 *lcaDeepestLeaves(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 *lcaDeepestLeaves(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* lcaDeepestLeaves(TreeNode* root) {if (root == nullptr) return nullptr;TreeNode* left = root->left, *right = root->right;TreeNode* lcaLeft = lcaDeepestLeaves(root->left), *lcaRight = lcaDeepestLeaves(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/101855.html

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

相关文章

lambda表达式介绍

前言 lambda表达式是C11标准才支持的&#xff0c;有了它以后在一些地方进行使用会方便很多&#xff0c;尤其在一些需要仿函数的地方&#xff0c;lambda表达式完全可以替代它的功能。代码的可读性也会提高。 目录 1.lambda表达式 2.lambda表达式语法 3.函数对象和lambda表达…

最新暴力破解漏洞技术详解

点击星标&#xff0c;即时接收最新推文 本文选自《web安全攻防渗透测试实战指南&#xff08;第2版&#xff09;》 点击图片五折购书 暴力破解漏洞简介 暴力破解漏洞的产生是由于服务器端没有做限制&#xff0c;导致攻击者可以通过暴力的手段破解所需信息&#xff0c;如用户名、…

日200亿次调用,喜马拉雅网关的架构设计

说在前面 在40岁老架构师 尼恩的读者社区(50)中&#xff0c;很多小伙伴拿到一线互联网企业如阿里、网易、有赞、希音、百度、滴滴的面试资格。 最近&#xff0c;尼恩指导一个小伙伴简历&#xff0c;写了一个《API网关项目》&#xff0c;此项目帮这个小伙拿到 字节/阿里/微博/…

Matlab 如何把频谱图的纵坐标设置为分贝刻度

Matlab 如何把频谱图的纵坐标设置为分贝刻度 Matlab代码如下&#xff1a; % 如何把频谱图的纵坐标设置为分贝刻度 % % pr2_2_6 clc; clear; close all;load pr2_2_6_sndata1.mat % 读入数据 X fft(y); % FFT n2 1:L/21; % 计算正频率…

【AI理论学习】语言模型:从Word Embedding到ELMo

语言模型&#xff1a;从Word Embedding到ELMo ELMo原理Bi-LM总结参考资料 本文主要介绍一种建立在LSTM基础上的ELMo预训练模型。2013年的Word2Vec及2014年的GloVe的工作中&#xff0c;每个词对应一个vector&#xff0c;对于多义词无能为力。ELMo的工作对于此&#xff0c;提出了…

Linux入门之多线程|线程的同步|生产消费模型

文章目录 一、多线程的同步 1.概念 2.条件变量 2.1条件变量概念 2.2条件变量接口 1.条件变量初始化 2.等待条件满足 3.唤醒等待 3.销毁条件变量 2.3条件变量demo 二、生产消费模型 1.生产消费模型 2.基于BlockQueue的生产者消费者模型 3.基于C用条件变量和互斥锁实…

c语言数组的用法

c语言数组的用法如下&#xff1a; 一维数组的定义方式 在C语言中使用数组必须先进行定义。一维数组的定义方式为&#xff1a; 类型说明符 数组名 [常量表达式]; 其中&#xff0c;类型说明符是任一种基本数据类型或构造数据类型。数组名是用户定义的数组标识符。方括号中的常量表…

9月7日扒面经

redis缓存用在哪里&#xff0c;用本地缓存行不行? 数据库查询缓存&#xff0c;减小数据源压力&#xff0c;提高响应速度 页面缓存&#xff1a;将页面的渲染结果缓存在Redis中&#xff0c;以减少页面生成的时间和服务器负载。 频繁计算结果缓存&#xff1a;将频繁计算的结果…

stable diffusion实践操作-随机种子seed

系列文章目录 stable diffusion实践操作 文章目录 系列文章目录前言一、seed是什么&#xff1f;二、使用步骤1.多批次随机生成多张图片2.提取图片seed3. 根据seed 再次培养4 seed使用4.1 复原别人图4.1 轻微修改4.2 固定某个人物-修改背景 三、差异随机种子1. webUI位置2. 什么…

maven打包时显示无效jdk版本

1、配置当前项目所需的Jdk版本 2、与当前项目指定的jdk版本相同 3、与当前项目指定的jdk版本相同 4、与当前项目指定的jdk版本相同 5、指定主项目启动时的vm配置与当前项目所需版本相同

Linux系统离线安装RabbitMQ

安装rabbitmq 1、下载安装包 首先进入官网进行安装包的下载&#xff0c;在下载时一定要注意erlong版本和rabbitmq-server版本匹配 rabbitmq版本对应关系&#xff1a;传送门 Erlong下载地址:传送门 rabbitmq-server下载地址:传送门 socat 不同版本 centos7:传送门 cent…

nas汇编程序的调试排错方法

nas汇编程序的调试排错方法&#xff1a; 1、查找是哪一步错了 2、查看对应的*.lst文件&#xff0c;本例中是"asmhead.lst" 3、根据*.lst文件的[ERROR #002]提示查看源码&#xff0c;改错。 4、重新运行编译&#xff0c;OK 1、查找是哪一步错了&#xff1a; nask.ex…