【算法题解】51. 二叉树的最近公共祖先

这是一道 中等难度 的题

https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/

题目

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 pq,最近公共祖先表示为一个节点 x,满足 xpq 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

示例 1:

输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1 
输出:3 
解释:节点 5 和节点 1 的最近公共祖先是节点 3 。 

示例 2:

输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4 
输出:5 
解释:节点 5 和节点 4 的最近公共祖先是节点 5 。因为根据定义最近公共祖先节点可以为节点本身。 

示例 3:

输入:root = [1,2], p = 1, q = 2 
输出:1 

提示:

  • 树中节点数目在范围 [ 2 , 1 0 5 ] [2, 10^5] [2,105] 内。
  • − 1 0 9 < = N o d e . v a l < = 1 0 9 -10^9 <= Node.val <= 10^9 109<=Node.val<=109
  • 所有 N o d e . v a l Node.val Node.val 互不相同 。
  • p != q
  • p 和 q 均存在于给定的二叉树中。

题解

公共祖先的定义:以这个节点为根节点的二叉树中,同时包含有 节点p节点q

判断一颗树是否包含 节点p ,只要满足以下条件中的一个即可:

  1. 根节点就是 节点p
  2. 左子树包含 节点p
  3. 右子树包含 节点p

很明显的可以看出条件23是这个问题的子问题,首先考虑递归的思路去实现。

Java 代码为例:

private boolean dfs(TreeNode node, TreeNode p){// 边界条件if(node == null){return false;}// 满足条件1if(node == p){return true;}// 满足 条件2 或 条件3return dfs(node.left) || dfs(node.right);
}

同样的,判断是否包含 节点q 也是上面的逻辑。

因为判定是否是公共祖先,需要同时判定是否包含 节点p节点q,所以需要对上面的递归函数稍微改进一下。

我们可以让递归函数返回一个 boolean 数组 has[]来表示是否包含节点p节点q,其中:

  1. h a s [ 0 ] = = t r u e has[0] == true has[0]==true 表示包含 节点p h a s [ 0 ] = = f a l s e has[0] == false has[0]==false 表示不包含。
  2. h a s [ 1 ] = = t r u e has[1] == true has[1]==true 表示包含 节点q h a s [ 1 ] = = f a l s e has[1] == false has[1]==false 表示不包含。

改进后的代码实现为:

private boolean[] dfs(TreeNode node, TreeNode p, TreeNode q){boolean[] has = new boolean[2];// 边界条件if(node == null){return hase;}// 满足条件1if(node == p){has[0] = true;}if(node == q){has[1] = true;}boolean[] leftHas = dfs(node.left, p, q);boolean[] rightHas = dfs(node.left, p, q);// 满足 条件2 或 条件3has[0] = has[0] || leftHas[0] || rightHas[0];has[1] = has[1] || leftHas[1] || rightHas[1];// 只要满足 has[0] 和 has[1] 都为true// 就说明这个节点 node 是节点 p 和 q 的公共祖先。return has;
}

通过上述递归函数我们可以求得 节点p节点q 的所有公共祖先,但是题目要求的是求得 最近公共祖先

最近公共祖先的定义:所有公共祖先当中,深度最大的那个即为最近公共祖先。

因为递归函数是从上往下递归的,答案的得出是从下往上回溯的。所以最先知道其是公共祖先的那个节点就是最近公共祖先。

假设答案为 ans,当知道节点 node 是公共祖先且 ans 为空 的时候,节点 node 就是答案。因为 ans 不空的时候,node 已经不是最深处的那个公共祖先了。

完整代码见代码实现。

Java 代码实现

/*** Definition for a binary tree node.* public class TreeNode {*     int val;*     TreeNode left;*     TreeNode right;*     TreeNode(int x) { val = x; }* }*/
class Solution {private TreeNode ans = null;public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {dfs(root, p, q);return ans;}private boolean[] dfs(TreeNode node, TreeNode p, TreeNode q){boolean[] has = new boolean[2];// 边界条件if(node == null){return has;}// 满足条件1if(node == p){has[0] = true;}if(node == q){has[1] = true;}boolean[] leftHas = dfs(node.left, p, q);boolean[] rightHas = dfs(node.right, p, q);// 满足 条件2 或 条件3has[0] = has[0] || leftHas[0] || rightHas[0];has[1] = has[1] || leftHas[1] || rightHas[1];// 最先知道答案的即为最近公共祖先if(ans == null && has[0] && has[1]){ans = node;}return has;}}

Go 代码实现

/*** Definition for a binary tree node.* type TreeNode struct {*     Val int*     Left *TreeNode*     Right *TreeNode* }*/
func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode {var ans *TreeNodevar dfs func(node *TreeNode) []booldfs = func(node *TreeNode) []bool {has := []bool{false, false}if node == nil {return has}if node == p {has[0] = true}if node == q {has[1] = true}leftHas := dfs(node.Left)rightHas := dfs(node.Right)has[0] = has[0] || leftHas[0] || rightHas[0]has[1] = has[1] || leftHas[1] || rightHas[1]if ans == nil && has[0] && has[1] {ans = node}return has}dfs(root)return ans}

复杂度分析

时间复杂度: O ( N ) O(N) O(N)N 为二叉树中节点的个数,每个节点都需要遍历一次。

空间复杂度: O ( N ) O(N) O(N)N 为二叉树中节点的个数,空间复杂度取决于递归调用栈的深度,最大为 N

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

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

相关文章

【模型压缩】 LPPN论文阅读笔记

LPPN论文阅读笔记 LPPN: A Lightweight Network for Fast Phase Picking 背景 深度学习模型的问题在于计算复杂度较高&#xff0c;在实际数据处理中需要面临较高的处理代价&#xff0c;且需要专用的加速处理设备&#xff0c;如GPU。随着数据累积&#xff0c;迫切需要设计一种…

【力扣刷题 | 第二十二天】

目录 前言&#xff1a; 63. 不同路径 II - 力扣&#xff08;LeetCode&#xff09; 343. 整数拆分 - 力扣&#xff08;LeetCode&#xff09; 总结&#xff1a; 前言&#xff1a; 今天我们爆刷动态规划章节的题目&#xff0c;相关的算法理论介绍我也有写过文章&#xff1a;【夜…

深度学习anaconda+pycharm+虚拟环境迁移

一、下载好anaconda和pycharm安装包。 下载anaconda:Index of /anaconda/archive/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror pycharm汉化包 二、安装anaconda 深度学习环境配置-Anaconda以及pytorch1.2.0的环境配置&#xff08;Bubbliiiing 深度学习 教程&…

Java版本企业电子招标采购系统源码:营造全面规范安全的电子招投标环境,促进招投标市场健康可持续发展

营造全面规范安全的电子招投标环境&#xff0c;促进招投标市场健康可持续发展 传统采购模式面临的挑战 一、立项管理 1、招标立项申请 功能点&#xff1a;招标类项目立项申请入口&#xff0c;用户可以保存为草稿&#xff0c;提交。 2、非招标立项申请 功能点&#xff1a;非招标…

uniapp小程序跳转其他小程序uni.navigateToMiniProgram效果demo(整理)

放点击事件里面即可 uni.navigateToMiniProgram({appId: , //跳转的小程序的aooIdpath: pages/index/index?id123, //如果这里不填&#xff0c;默认是跳转到对方小程序的主页面extraData: { //需要传给对方小程序的数据data1: test},success(res) {// 打开成功} })

JAVA设计模式——单例模式

单例模式是应用最广的设计模式之一&#xff0c;也是程序员最熟悉的一个设计模式&#xff0c;使用单例模式的类必须保证只能有创建一个对象。 今天主要是回顾一下单例模式&#xff0c;主要是想搞懂以下几个问题 为什么要使用单例&#xff1f; 如何实现一个单例&#xff1f; 单…

c++11/c++98动态规划入门第5课,经典DP问题 --- 区间

第1题 取数问题 查看测评数据信息 有一排N个数&#xff0c;你和小明2个人玩游戏&#xff0c;每个人轮流从2端取数&#xff0c;每次可以从左或右取&#xff0c;不能从中间取。你取的所有的数的和是你的得分&#xff0c;小明取的所有的数的和是小明的得分。如果你先取&#x…

【图像分割】基于蜣螂优化算法DBO的Otsu(大津法)多阈值电表数字图像分割 电表数字识别【Matlab代码#51】

文章目录 【可更换其他算法&#xff0c;获取资源请见文章第5节&#xff1a;资源获取】1. 原始蜣螂优化算法1.1 滚球行为1.2 跳舞行为1.3 繁殖行为1.4 偷窃行为 2. 多阈值Otsu原理3. 部分代码展示4. 仿真结果展示5. 资源获取说明 【可更换其他算法&#xff0c;获取资源请见文章第…

springboot 项目启动不打印spring 启动日志

今天项目遇到一个很奇怪的问题&#xff0c;服务在启动时&#xff0c;不打印spring 的启动日志。经过排查发现是因为其他的依赖引入了 log4j 的依赖&#xff0c;因为我们的项目用的是logback&#xff0c;所以项目中没有log4j 的相关配置&#xff0c;所以干扰到了日志的打印 原因…

Vue入门项目——WebApi

Vue入门——WebApi vue3项目搭建组合式API响应式APIreactive()ref() 生命周期钩子computed计算属性函数watch监听函数父子通信模板引用组合选项 vue3项目搭建 简单看下Vue3的优势吧 下载安装npm及node.js16.0以上版本&#xff08;确保安装成功可用如下代码检查版本&#xff0…

工厂电能质量治理解决方案

1、概述 谐波的危害十分严重&#xff0c;尤其在工厂这种设备较多的场合。大部分设备都是谐波源&#xff0c;谐波使电能的生产、传输和利用的效率降低&#xff0c;使电气设备过热、产生振动和噪声&#xff0c;并使绝缘老化&#xff0c;使用寿命缩短&#xff0c;甚至发生故障或烧…

RocketMQ 5.0 无状态实时性消费详解

作者&#xff1a;绍舒 背景 RocketMQ 5.0 版本引入了 Proxy 模块、无状态 pop 消费机制和 gRPC 协议等创新功能&#xff0c;同时还推出了一种全新的客户端类型&#xff1a;SimpleConsumer。 SimpleConsumer 客户端采用了无状态的 pop 机制&#xff0c;彻底解决了在客户端发布…

QT字节数组类QByteArray

QT字节数组类QByteArray 初始化访问某个元素截取字符串获取字节数组的大小数据转换与处理Hex转换数值转换与输出 字母大小写转换字符串数值转化为各类数值QBQyteArray和char*互转QByteArray 和std::string互转与字符串QString互转QByteArray和自定义结构体之间的转化判断是否为…

区块链实验室(11) - PBFT耗时与流量特征

以前面仿真程序为例&#xff0c;分析PBFT的耗时与流量特征。实验如下&#xff0c;100个节点构成1个无标度网络&#xff0c;节点最小度为5&#xff0c;最大度为38. 从每个节点发起1次交易共识。统计每次交易的耗时以及流量。本文所述的流量见前述仿真程序的说明:区块链实验室(3)…

13.4.2 【Linux】sudo

相对于 su 需要了解新切换的使用者密码 &#xff08;常常是需要 root 的密码&#xff09;&#xff0c; sudo 的执行则仅需要自己的密码即可。sudo 可以让你以其他用户的身份执行指令 &#xff08;通常是使用 root 的身份来执行指令&#xff09;&#xff0c;因此并非所有人都能够…

AcWing 1210. 连号区间数

输入样例1&#xff1a; 4 3 2 4 1输出样例1&#xff1a; 7输入样例2&#xff1a; 5 3 4 2 5 1输出样例2&#xff1a; 9样例解释 第一个用例中&#xff0c;有 77 个连号区间分别是&#xff1a;[1,1],[1,2],[1,3],[1,4],[2,2],[3,3],[4,4][1,1],[1,2],[1,3],[1,4],[2,2],[3,3…

Linux系统知识1—Linux命令基础格式,什么是命令,命令行,ls命令入门,ls命令的参数和选项,-a,-l -h选项的使用及组合使用

一.什么是命令&#xff0c;命令行 &#xff0e;命令行&#xff1a;即 Linux 终端&#xff08; Terminal )&#xff0c;是一种命令提示符页面。以纯"字符"的形式操作系统&#xff0c;可以使用各种字符化命令对系统发出操作指令。 &#xff0e;命令&#xff1a;即 Lin…

redis(9):spring里面使用redis

1 创建一个mave项目 自行创建一个maven项目 2 修改pom.xml <properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>1.7</maven.compiler.source><maven.compiler.target>1.7</maven…

SpringMVC

文章目录 一.SpringMVC的概念1.1 Spring的概念1.2 MVC和SpringMVC的关系 二.SpringMVC步骤2.1 创建一个springMVC项目2.2 获取参数的功能2.3 输出参数的功能 三.SpringMVC的注解介绍3.1 获取参数3.1.1 获取参数单个参数3.1.2 获取多个参数3.1.3获取对象3.1.4 获取json对象3.1.5…

深度学习常用优化器总结,具详细(SGD,Momentum,AdaGrad,Rmsprop,Adam,Adamw)

学习需要&#xff0c;总结一些常用优化器。 目录 前言SGD&#xff1a;随机梯度下降BGD&#xff1a;批量梯度下降MBGD&#xff1a;小批量梯度下降MomentumAdaGradRMSpropAdam: Adaptive Moment EstimationAdamW参考文章 前言 优化器的本质是使用不同的策略进行参数更新。常用的…