LeetCode236. 二叉树的最近公共祖先

news2025/1/10 11:19:10

236. 二叉树的最近公共祖先

文章目录

      • [236. 二叉树的最近公共祖先](https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/)
        • 一、题目
        • 二、题解
          • 方法一:递归构建祖先数组
          • 方法二:一个非常方便的递归


一、题目

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

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

示例 1:

img

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

示例 2:

img

输入: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, 105] 内。
  • -109 <= Node.val <= 109
  • 所有 Node.val 互不相同
  • p != q
  • pq 均存在于给定的二叉树中。

二、题解

方法一:递归构建祖先数组

当解决这类二叉树相关的问题时,我们可以考虑使用递归来遍历树的节点。在这个问题中,我们需要找到两个指定节点的最近公共祖先,可以考虑从根节点开始递归地往下搜索。下面是一个详细的解题思路:

算法思路

  1. 我们从根节点开始递归遍历树,查找指定的节点p和q。
  2. 使用一个递归函数 findAncestor,它将用于找到一个节点的所有祖先节点。我们使用两个vector分别保存节点p和q的所有祖先节点。
  3. 在递归遍历的过程中,一旦找到了节点p或q,我们将停止继续递归并且将flag设置为true,以便在后续递归中可以直接返回而不继续递归。
  4. findAncestor 中,我们首先递归地遍历左子树,然后递归地遍历右子树。如果已经找到了节点p或q(flag为true),或者当前节点就是目标节点之一,我们将当前节点添加到对应的祖先节点vector中,并将flag设置为true。
  5. 于是乎,在递归回溯过程中目标节点的祖先节点会不断进入祖先数组节点vector(所以最后得到的数组是倒序的)。
  6. lowestCommonAncestor 函数中,我们先使用 findAncestor 分别找到节点p和q的所有祖先节点。
  7. 然后,我们遍历这两个祖先节点vector,寻找第一个在两个vector中都出现的节点。这个节点就是两个指定节点的最近公共祖先。

具体实现

class Solution {
public:
    TreeNode* pre = nullptr;
    bool flag = false;
    
    // 递归函数,用于查找节点的所有祖先节点
    void findAncestor(vector<TreeNode*>& ances, TreeNode* root, TreeNode* target) {
        if (root == nullptr) return;
        if (flag) return; 
        
        // 递归遍历左子树
        findAncestor(ances, root->left, target);
        // 递归遍历右子树
        findAncestor(ances, root->right, target);
        
        // 如果flag为true,表示已经找到目标节点,或者当前节点就是目标节点之一
        if (flag || root == target) {
            // 将当前节点添加到祖先节点vector中
            ances.push_back(root);
            // 设置flag为true,以便在后续递归中可以直接返回
            flag = true;  
        }
    }

    // 主函数,寻找两个指定节点的最近公共祖先
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        TreeNode* result = nullptr;
        vector<TreeNode*> ancesP;
        vector<TreeNode*> ancesQ;
        
        if (root == nullptr) return result;
        
        // 找到节点p的所有祖先节点
        findAncestor(ancesP, root, p);
        flag = false; 
        // 找到节点q的所有祖先节点
        findAncestor(ancesQ, root, q);
        
        // 遍历节点p的祖先节点
        for (int i = 0; i < ancesP.size() && result == nullptr; i++) {
            // 遍历节点q的祖先节点
            for (int j = 0; j < ancesQ.size(); j++) {
                // 如果在两个祖先节点vector中找到相同的节点,就是最近公共祖先
                if (ancesP[i] == ancesQ[j]) {
                    result = ancesP[i];
                    break;
                }
            }
        }
        return result;
    }
};

算法分析

  • 时间复杂度:遍历树的过程中,每个节点都会被访问一次,所以时间复杂度为O(n),其中n是树中的节点数。
  • 空间复杂度:递归栈的深度最多为树的高度,而空间复杂度取决于递归栈的最大深度,所以空间复杂度为O(h),其中h是树的高度。此外,用于存储祖先节点的vector也会占用一些空间。

错误历程

第一次写出来的时候犯了一些错误,花了很久才改过来,代码是这样的,错误原因写在了里面:

class Solution {
public:
    TreeNode* pre = nullptr;
    bool flag = false;
    
    void findAncestor(vector<TreeNode*>& ances, TreeNode* root, int target) {
        if (root == nullptr) return;
        findAncestor(ances, root->left, target);
        flag = false;//首先代码是基于后序遍历的。加了这行以后如果target在左子树,最后祖先数组会少根节点(调试就可以调试出来),但是不加这行更是错误,因为target在左子树的话连右子树节点都会成为其祖先节点,所以最好的方案是上面答案前面加上if (flag) return; 然后lowCommonAncestor中两个findAncestor之间重置一下flag,啥问题都解决了。
        findAncestor(ances, root->right, target);
        if (flag) {
            ances.push_back(root);
            return;
        }
        if (root->val == target) {
            ances.push_back(root);
            flag = true;
            return;
        }
    }

    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        TreeNode* result = nullptr;
        vector<TreeNode*> ancesP;
        vector<TreeNode*> ancesQ;
        
        if (root == nullptr) return result;
        
        findAncestor(ancesP, root, p->val);
        findAncestor(ancesQ, root, q->val);
        
        for (int i = 0; i < ancesP.size(); i++) {
            for (int j = 0; j < ancesQ.size(); j++) {
                if (ancesP[i] == ancesQ[j]) {
                    result = ancesP[i];
                    return result;
                }
            }
        }
        return result;
    }
};
方法二:一个非常方便的递归

只能说很巧妙……

算法思路

  1. 递归的终止条件: 首先,我们需要考虑递归的终止条件。如果当前节点为空,或者当前节点是要找的两个节点之一,那么直接返回当前节点。这是因为,当节点为空时,肯定不存在公共祖先;而当节点等于要找的节点之一时,该节点自身就是最近公共祖先。

  2. 递归搜索左右子树: 若不满足终止条件,说明当前节点既不为空,也不是要找的节点之一。我们需要递归地搜索左子树和右子树,来寻找节点p和q的最近公共祖先。

  3. 递归合并结果: 在搜索左右子树之后,我们可以获得左子树和右子树分别关于节点p和q的最近公共祖先。如果左右子树的结果都不为空,说明节点p和q分别位于左右子树的不同侧,而当前节点就是最近公共祖先。如果左子树的结果不为空,而右子树的结果为空,说明两个节点都在左子树上,最近公共祖先就在左子树中。如果左子树的结果为空,而右子树的结果不为空,情况类似。

具体实现

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        // 递归终止条件:节点为空或节点是要找的节点之一
        if (root == nullptr || root == p || root == q) {
            return root;
        }

        // 递归搜索左右子树
        TreeNode* left = lowestCommonAncestor(root->left, p, q);
        TreeNode* right = lowestCommonAncestor(root->right, p, q);

        // 递归合并结果
        if (left && right) {
            return root;
        } else if (left) {
            return left;
        } else {
            return right;
        }
    }
};

算法分析

  • 时间复杂度:在最坏情况下,每个节点都会被访问一次,所以时间复杂度为O(n),其中n是树中的节点数。
  • 空间复杂度:递归栈的深度最多为树的高度,而空间复杂度取决于递归栈的最大深度,所以空间复杂度为O(h),其中h是树的高度。

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

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

相关文章

性能测试|App性能测试需要关注的指标

一、Android客户端性能测试常见指标&#xff1a; 1、内存 2、CPU 3、流量 4、电量 5、启动速度 6、滑动速度、界面切换速度 7、与服务器交互的网络速度 二、预期标准指定原则 1、分析竞争对手的产品&#xff0c;所有指标要强于竞品 2、产品经理给出的预期性能指标数据…

青翼科技自研2路250MSPS DA回放FMC子卡模块

FMC150_V30是一款基于VITA57.1规范的2路125MSPS采样率16位分辨率AD采集、2路250MSPS采样率16位分辨率DA回放FMC子卡模块。该模块遵循VITA57.1规范&#xff0c;可直接与符合VITA57.1规范的FPGA载卡配合使用&#xff0c;板卡ADC器件采用ADI公司的AD9268芯片&#xff0c;板卡DAC器…

DDIM: DENOISING DIFFUSION IMPLICIT MODELS

DDIM: DENOISING DIFFUSION IMPLICIT MODELS 去噪扩散隐式模型DDIM预测噪声生成过程 实验 论文题目&#xff1a;Denoising Diffusion Implicit Models (DDIM) 论文来源&#xff1a;ICLR 2021 论文地址&#xff1a;https://arxiv.org/pdf/2010.02502.pdf 论文代码&#xff1a;ht…

“之江数据安全治理论坛”暨《浙江省汽车数据处理活动规定(专家建议稿)》研讨会顺利召开

研讨会主题 8月10日&#xff0c;“之江数据安全治理论坛”暨《浙江省汽车数据处理活动规定&#xff08;专家建议稿&#xff09;》研讨会在浙江大学计算机创新技术研究院举办。 本次研讨会的主题聚焦于“智能网联汽车的数据安全与数据合规”&#xff0c;邀请行业主管部门和数据…

iptables与firewall的命令的使用

iptables与firewall的命令的使用 安装firewall指令基本使用iptables简介命令查看 IPTABLES 版本查看当前 IPTABLES 规则添加规则修改规则删除某条规则加入白名单备份与还原定义策略 其他 安装 查看是否已安装&#xff1a; CentOS&#xff1a;rpm -qa | grep iptables 安装&am…

推荐一个好用的程序员工具箱,集成了代码生成,云剪切板以及AI能力

给大家推荐一个程序员必备网站&#xff0c;功能方便、实用&#xff0c;页面精美&#xff0c;下面是部分功能截图: 代码生成 云剪切板 工作日报生成 专利文章生成 毕业论文生成 地址: http://119.29.247.153/#/ai-tools​

计算机组成部分

计算机的五大部件是什么&#xff1f;答案&#xff1a;计算机的五大部件是运算器&#xff0c;控制器&#xff0c;存储器&#xff0c;输入设备和输出设备。 其中运算器和控制器合称中央处理器&#xff0c;是计算机的核心部件&#xff1b; 存储器是用来存储程序指令和数据用的&am…

免费AI作图项目:Fooocus,Github高星推荐,支持win私有化部署

我们都知道&#xff0c;Midjourney是需要付费使用的&#xff0c;而Stable Diffusion需要大量的学习成本&#xff0c;在这种背景下&#xff0c;Fooocus应运而生。 Fooocus 是一款图像生成软件。 Fooocus项目原地址&#xff1a;https://github.com/lllyasviel/Fooocus Stable …

华为手机Outlook手机APP无法登录邮箱,提示[2002]错误代码

近期遇到不少华为手机的Outlook APP无法登录邮箱Office365邮箱的案例&#xff0c;并且提示&#xff1a; 错误 出错了。[2002] 经测试&#xff0c;这应该是华为应用市场下载的Outlook版本有问题。 解决方法&#xff1a; 把Outlook卸载之后从微软官网重新下载官网版本去安装&am…

全网最细,Jmeter接口测试-实现动态关联实战整理,你要的都有...

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

http库 之 OKHttpUtil

源码位置 方便实用&#xff0c;个人感觉不错 依赖 <dependency><groupId>io.github.admin4j</groupId><artifactId>common-http-starter</artifactId><version>0.7.5</version> </dependency>代码实践 /*** 通用http的pos…

SringBoot-响应

响应数据 如何加载响应数据呢 其实在SpringBoot&#xff0c;已经有名为RessponseBody的方法注解为我们提供的响应的方法&#xff0c;他的作用是将方法返回值直接响应&#xff0c;如果返回值类型为实体对象/集合&#xff0c;则会转换为JSON格式响应。 而RestController已经在内…

Java后端开发需要掌握什么知识和技能?干货来了

Java作为一种广泛应用于软件开发中的编程语言&#xff0c;其独特的编写风格、代码复用性以及多平台兼容性等特点&#xff0c;使其成为软件开发领域中最受欢迎的编程语言之一。然而&#xff0c;Java后端开发需要掌握什么知识和技能?下面就让小编为大家介绍一些Java开发常用的知…

idea格式化日志打印

Live Template 需要在Live Templates里面创建一个模板组为MyTemplate 触发时机选择java 1、创建一个loge log.error($content$,$params$); content groovyScript("def params _3.collect {【it {}】}.join(, ); return \" _1 . _2 () exception > (params…

【kubernetes】k8s高可用集群搭建(三主三从)

目录 【kubernetes】k8s高可用集群搭建&#xff08;三主三从&#xff09; 一、服务器设置 二、环境配置 1、关闭防火墙 2、关闭selinux 3、关闭swap 4、修改主机名&#xff08;根据主机角色不同&#xff0c;做相应修改&#xff09; 5、主机名映射 6、将桥接的IPv4流量…

6-1_ADC示例分析

1.参考代码示例 PRJ_M66_4.3.3\boards\apollo4l_blue_eb\examples\peripherals\adc_measure\src\adc_measure.c 本篇基于Apollo4BlueLite 4.3.3版本的SDK中adc_measure.c示例的代码进行分析。 2. 代码流程 &#xff08;1&#xff09;初始化一路GPIO作为ADC输入 &#xff0…

P4500Q22CLRP 半导体放电管 品牌厂家 现货直供

防浪涌过电压保护电路中&#xff0c;常用的过电压保护器件有&#xff1a;半导体放电管TSS、TVS瞬态抑制二极管、压敏电阻MOV、陶瓷气体放电管GDT&#xff0c;其中半导体放电管TSS和陶瓷气体放电管GDT属于开关型过压保护器件&#xff0c;压敏电阻MOV和TVS瞬态抑制二极管属于钳位…

金蝶软件实现导入Excel数据分录行信息到单据体分录行中

>>>适合KIS云专业版V16.0|KIS云旗舰版V7.0|K/3 WISE 14.0等版本<<< 金蝶软件中实现[导入Excel数据业务分录行]信息到[金蝶单据体分录]中,在采购订单|采购入库单|销售订单|销售出库单等类型单据中,以少量的必要字段在excel表格中按模板填列好,很方便快捷地从…

app测试和web测试有什么区别

1.性能方面: web页面可能更关注响应时间&#xff0c;而app更关注流量、电量、QPS。 2.系统架构方面&#xff1a; web项目&#xff0c;一般都是b/s架构&#xff0c;基于浏览器的&#xff0c;而app则是c/s的&#xff0c;必须要有客户端。在系统测试的时候就会产生区别了。首从…

使用ntp服务器调整linux系统时间(附带代码示例)

前言 这是我在这个网站整理的笔记&#xff0c;关注我&#xff0c;接下来还会持续更新。 作者&#xff1a;RodmaChen 为了防止应用在系统上运行时候&#xff0c;系统时间与真实时间出现误差。可以调用ntp服务器获取正确的时间进行调整 NTP简介 网络时间协议&#xff08;NTP&…