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

news2025/1/8 5:29:16

这是一道 中等难度 的题

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;
    }
    // 满足条件1
    if(node == p){
        return true;
    }
    // 满足 条件2 或 条件3
	return 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;
    }

    // 满足条件1
    if(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 或 条件3
    has[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;
        }

        // 满足条件1
        if(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 或 条件3
        has[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 *TreeNode

    var dfs func(node *TreeNode) []bool

    dfs = 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.coloradmin.cn/o/787608.html

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

相关文章

Transformer+医学图像最新进展【2023】

Transformer主要用于自然语言处理领域。近年来,它在计算机视觉(CV)领域得到了广泛的应用。医学图像分析(MIA,Medical image analysis)作为机器视觉(CV,Computer Vision)的一个重要分支,也极大地受益于这一最先进的技术。 机构:新加坡国立大学机械工程系、中山大学智能系…

MySQL创建全文索引时,遇到“Temporary file write failure”的错误

MySQL创建全文索引时&#xff0c;遇到“Temporary file write failure”的错误 环境信息 MySQL Version: 8.0.28 engine: InnoDB rows: 100 index length: 10MB data length: 30MB 笔者在MYSQL上执行创建添加全文索引的语句&#xff1a;alter table users add fulltext index …

机器学习原理(1)集成学习基本方法

一.什么是集成学习 集成学习&#xff08;ensemble learning&#xff09;通过将多个学习器进行组合来完成学习任务。下图显示集成学习的一般结构&#xff08;取自周志华老师的西瓜书&#xff09;&#xff0c;个体学习器通常由一种现有的学习算法从训练数据产生&#xff0c;例如…

Vue项目实战失物招领

经过两天的时间&#xff0c;搞定了一个Vue版本的项目&#xff0c;在这里留下这两天的点点滴滴&#xff0c;这个项目主要实现了失物招领的相关功能&#xff0c;比如发布丢失信息&#xff0c;发布拾到信息&#xff0c;跑腿信息&#xff0c;用户注册&#xff0c;用户登录等相关功能…

故障分析 | Kubernetes 故障诊断流程

一、本文概述及主要术语 1.1 概述 本文基于 Pod 、Service 和 Ingress 三大模块进行划分&#xff0c;对于 Kubernetes 日常可能出现的故障问题&#xff0c;提供了较为具体的排查步骤&#xff0c;并附上相关解决方法或参考文献。 1.2 主要术语 Pod: Kubernetes 中创建和管理的…

Spring Boot日志:SLF4J和Logback

日志的分类 SpringBoot中的日志库分为两种&#xff1a; 实现库&#xff1a;提供具体的日志实现&#xff0c;例如日志级别的控制、打印格式、输出目标等。外观库&#xff1a;自身不提供日志实现&#xff0c;而是对其他日志库进行封装&#xff0c;从而方便使用。基于外观模式实…

接口自动化测试-Python+Requests+Pytest+YAML+Allure配套撸码(详细)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 接口自动化框架&a…

软件测试/测试开发丨Pytest测试框架学习笔记

Pytest 参数化用例 测试登录场景 测试登录成功&#xff0c;登录失败(账号错误&#xff0c;密码错误)*创建多种账号: 中⽂文账号&#xff0c;英⽂文账号*普通测试用例方法Copy 多份代码 or 读⼊入参数?*一次性执⾏多个输⼊入参数* def test_param_login_ok():# 登录成功user…

解决分类任务中数据倾斜问题

大家好&#xff0c;在处理文本分类任务时&#xff0c;基准测试流行的自然语言处理架构的性能是建立对可用选项的理解的重要步骤。在这里&#xff0c;本文将深入探讨与分类相关的最常见的挑战之一——数据倾斜。如果你曾经将机器学习&#xff08;ML&#xff09;应用于真实世界的…

selenium---滑动框验证码破解

前言 目前常见的验证码有很多种&#xff0c;比如数字验证码&#xff0c;滑动验证码&#xff0c;以及滑动补全图像验证码等&#xff0c;关于验证码的操作属于我们在UI自动化很大的一个障碍&#xff0c;今天安静来介绍下如何通过python来实现我们滑动验证码 滑动验证码 先来一…

MySQL之全文索引二三事

全文索引 MySQL全文索引是一种用于快速搜索文本字符串的索引&#xff0c;在MySQL数据库中&#xff0c;它可以用来提高文本搜索的效率。全文索引不同于普通索引&#xff0c;普通索引只是对列值进行排序&#xff0c;而全文索引则会对列的内容进行分词&#xff0c;并且对每个分词…

RocketMQ重复消费的解决方案::分布式锁直击面试!

文章目录 场景分析方法的幂等分布式锁Redis实现分布式锁抢锁的设计思路 分布式锁案例 直击面试rocketmq什么时候重复消费消息丢失的问题消息在哪里丢失发送端确保发送成功并且配合失败的业务处理消费端确保消息不丢失rocketmq 主从同步刷盘 场景分析 分布式系统架构中,队列是分…

go-zero学习 第六章 分布式事务dtm

go-zero学习 第六章 分布式事务dtm 1 参考文档2 官方示例3 go-zero使用dtm参考代码3.1 go-zero支持dtm 代码操作步骤※3.2 gozerodtm 代码操作步骤 4 注意事项4.1 grpc接口地址※4.2 动态调用过程4.3 dtm的回滚补偿4.4 barrier的空补偿、悬挂等4.5 barrier在rpc中本地事务 1 参…

多媒体工作中用到的小工具

安利下嵌入式多媒体用到的各种小工具 下面是同事们推荐的 以下是对这些软件的简要介绍: ocenaudio:ocenaudio是一款跨平台的音频编辑软件,它提供了直观的界面和丰富的功能,可以帮助用户进行音频剪辑、修复、转码等操作。 MKVToolNix:MKVToolNix是一款开源的多媒体容器格…

web-vim信息泄露

&#xff08;1&#xff09;知识补充 vim 交换文件名 在使用vim时会创建临时缓存文件&#xff0c;关闭vim时缓存文件则会被删除&#xff0c;当vim异常退出后&#xff0c;因为未处理缓存文件&#xff0c;导致可以通过缓存文件恢复原始文件内容   以 index.php 为例&#xff1…

Redis一主二从三哨兵模式

文章目录 Redis一主二从三哨兵模式环境配置实践配置主从配置哨兵模式 测试主从复制测试模拟master宕机恢复master Redis一主二从三哨兵模式 当你使用Redis作为主从复制的架构&#xff0c;并且希望在出现主节点故障时自动进行故障转移时&#xff0c;适用于一主而从三哨兵模式。…

android 面试题目之handler消息机制

Handler消息机制是Android里面很基础的东西&#xff0c;基本上属于必考题 一般会从如下几个方面来考查 实现原理&#xff0c;Handler/Message/MessageQueue/Looper 几个类的实现流程&#xff0c;Handler导致的内存泄露怎么处理主线程的Looper是什么时候创建的&#xff1b;如果…

3. Spring 更简单的读取和存储对象(五大类注解 方法注解)

目录 1. 存储 Bean 对象 1.1 配置扫描路径 1.2 添加注解存储 Bean 对象 1.2.1 Controller&#xff08;控制器存储&#xff09; 1.2.2 Service&#xff08;服务存储&#xff09; 1.2.3 Repository&#xff08;仓库存储&#xff09; 1.2.4 Component&#xff08;组件存储&…

C语言学习笔记 Ubuntu系统下部署gcc编译工具-01

在22.04版本 ubuntu系统下&#xff1a; 1.进行apt工具包更新 sudo apt-get update 2.安装gcc工具 sudo apt-get install -y gcc 3.创建一下文件&#xff0c;并编译它输出相应的内容 touch hello.c 4.使用gcc编译c源程序并运行 格式&#xff1a;gcc 源文件名 -o 生成的执行文…

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…