【LeetCode】【二叉树的最近公共祖先】

news2025/1/11 0:18:50

力扣

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

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 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, 105] 内。
-109 <= Node.val <= 109
所有 Node.val 互不相同 。
p != q
p 和 q 均存在于给定的二叉树中。

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

一个节点也可以是自己的祖先。

沿着根节点到当前结点的路径上的所有的结点都可以是自己的祖先 

如果这里是三叉链,结点带parent,转换成链表相交的做法就可以了,

但是很可惜,这里并不是三叉链

 规则:一个是左子树中的结点,一个是右子树中的结点,那么他就是最近公共祖先

1.如果这两个结点在我的左子树和右子树当中,那么我就是这两个结点的最近公共祖先

2.如果这两个结点都在我的左边,那么我就去查找我的左子树是不是最近公共祖先

3.如果这两个结点都在我的右边,那么我就去查找我的右子树是不是最近公共祖先

4.如果我就是其中的一个要查找的结点,另外一个结点是我的孩子,那么我就是最近公共祖先

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    bool Find(TreeNode* sub,TreeNode* x)
    {
        if(sub==nullptr)
        {
            return false;
        }
        if(sub==x)
        {
            return true;
        }
        //去当前结点的左子树去找x,或者去右子树去找x,只要有一个地方找到就行
        return Find(sub->left,x)||Find(sub->right,x);
    }

        // 如果这两个结点在我的左子树和右子树当中,那么我就是这两个结点的最近公共祖先
        // 如果这两个结点都在我的左边,那么我就去查找我的左子树是不是最近公共祖先
        // 如果这两个结点都在我的右边,那么我就去查找我的右子树是不是最近公共祖先
        
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if(root==nullptr)
            return nullptr;

        // 如果我就是其中的一个要查找的结点,另外一个结点是我的孩子,那么我就是最近公共祖先
        if(root==p||root==q)
            return root;
        
        bool p_In_Left,p_In_Right,q_In_Left,q_InRight;
        //到我当前结点的左子树中去寻找p,如果找到了,就不要再往右子树当中找了
        p_In_Left=Find(root->left,p);
        //如果p在左边,那么就一定不在右边
        //因为我们的p一定在这棵树里面
        p_In_Right=!p_In_Left;

        //如果q在左边,那么就一定不在右边
        //因为我们的q一定在这棵树里面
        q_In_Left=Find(root->left,q);
        q_InRight=!q_In_Left;

        //1、一个在左,一个在右,那么root就是最近公共祖先
        //2、如果都在左,那么递归去左子树找
        //3、如果都在右,那么递归去右子树找
        if((p_In_Left&&q_InRight)||(q_In_Left&&p_In_Right))
        {
            return root;
        }
        //2、如果都在左,那么递归去左子树找
        else if(p_In_Left&&q_In_Left)
        {
            return lowestCommonAncestor(root->left, p,q);
        }
        //3、如果都在右,那么递归去右子树找
        else if(p_In_Right&&q_InRight)
        {
            return lowestCommonAncestor(root->right, p,q);
        }
        else
        {
            //理论而言,不会走这里。
            return nullptr;
        }
    }
};

 

 如果我们的要查找的两个结点越在我们的树的下面的结点,我们的上面这种算法的时间和空间开销就越大。

这里的find的时间复杂度就是O(N)

最多要找二叉树高度次(H)

也就是时间复杂度为O(N*H)

如果这里是一棵搜索二叉树,也就是左子树比根节点小,右子树比根节点都打,那我们就不需要这里的find函数了,也就可以提升我们的效率了。 

如何从O(N*H)优化到O(N),

如果我们这道题能够找到根节点到目标节点的路径,那么我们就能够转换为链表相交的方法去做了。现在我们就可以尝试寻找这两条路径

(这里我们按照前序遍历)

如果我们这里寻找6,当前我们的根节点,

3不是6,入栈3

5不是6,5入栈

6是6,找到了,再层层返回出栈

如果我们现在要找4,

3不是4,入栈4

5不是4,入栈5

6不是4,入栈6 

6的左右都是nullptr,都返回的是false

此时我们可以确定6这棵子树下面没有我们的目标结点了,将6出栈

接着2不是4,入栈2

7不是4,入栈2

7的左右结点都是nullptr,返回都是false

此时我们可以确定7这棵子树下面没有我们的目标节点了,将7出栈

4是我们的要找的结点,返回true,停止查找。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    bool FindPath(TreeNode* root,TreeNode* x,stack<TreeNode*>& path)
    {
        //这里返回的false和true都是提供给上一层的。
        if(root==nullptr)
            return false;

        path.push(root);
        if(root==x)
            return true;

        //左树找到了,右树就不要找了
        if(FindPath(root->left,x,path))
            return true;
        //左树没找到,就再去右树找
        if(FindPath(root->right,x,path))
            return true;

        //左树和右树都没有找到
        path.pop();
        //这里的false代表当前子树里面没有我们想要查找的结点
        return false;
    }
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        //创建两个栈用于存储从根节点到目标节点的路径
        stack<TreeNode*> pPath,qPath;
        FindPath(root,p,pPath);
        FindPath(root,q,qPath);
        
        //类似链表相交
        //让长的先走
        while(pPath.size()!=qPath.size())
        {
            //我们的栈刚好是反着存的,也就是树的底部的结点存在栈的顶部,root结点在栈的底部
            if(pPath.size()>qPath.size())
                pPath.pop();
            else
                qPath.pop();
        }
        while(pPath.top()!=qPath.top())
        {
            pPath.pop();
            qPath.pop();
        }
        //这两个路径是一定会有交点的
        //这里返回pPath或者qPath的top都是可以的。
        return pPath.top();
    }
};

 

 这一种思路就是将我们的时间复杂度降低到了O(N),也就是最多遍历一遍二叉树的所有结点。并且我们这一种方法相较于我们的上一种方法,由于我们这里开辟了两个栈,所以空间复杂度比较大。

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

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

相关文章

《JavaSE-第十四章》之文件(一)

前言 在你立足处深挖下去,就会有泉水涌出!别管蒙昧者们叫嚷:“下边永远是地狱!” 博客主页&#xff1a;KC老衲爱尼姑的博客主页 博主的github&#xff0c;平常所写代码皆在于此 刷题求职神器 共勉&#xff1a;talk is cheap, show me the code 作者是爪哇岛的新手&#xff0c;水…

Go 语言中的并发编程(Let‘s Go 二十八)

Go从底层就支持并发编程&#xff0c;同时实现垃圾回收机制。 先了解并发相关的几个名词概念。 进程&#xff1a;系统进行资源分配和调度的基本单位&#xff0c;使系统结构的基础&#xff0c;它是一个实体。 线程&#xff1a;是程序中一个单一的顺序执行流程&#xff0c;是进程…

python做了个自动关机工具,再也不会耽误我下班啦

上班族经常会遇到这样情况&#xff0c;着急下班结果将关机误点成重启&#xff0c;或者临近下班又通知开会&#xff0c;开完会已经迟了还要去给电脑关机。 【阅读全文】 今天使用PyQt5做了个自动关机的小工具&#xff0c;设置好关机时间然后直接提交即可&#xff0c;下班就可以…

什么是JWT及在JAVA中如何使用?

目录 1、为什么使用JWT? 2、JWT 的 格式 3、使用 JWT 就绝对安全 吗&#xff1f; 4、JWT 的 鉴权 流程 5、JWT 入门案例 5.1 引入依赖 5.2 生成Token 5.3 解析Token 5.4 工具类 JSON Web token简称JWT&#xff0c; 是用于对应用程序上的用户进行身份验证的标记。 也就…

测试中台初始设置

1.0 测试资源池 存在测试资源之后&#xff0c;将测试资源进行统一的管理&#xff0c;针对不同的测试场景和目的来定义不同的测试环境。将测试资源进行组合&#xff0c;测试平台可以对测试资源池进行统一的功能。 2.0 可用性测试及冒烟测试 可用性测试和冒烟测试都是快速验证…

基于Ubuntu搭建CTFd平台(全网最全)

前言&#xff1a; 最近在看《CTF安全竞赛入门》这本书&#xff0c;里面提到了搭建CTFd平台用于练习&#xff0c;学者可以在本地虚拟机上搭建。 所需系统&#xff1a;Ubuntu20.04 怎么安装虚拟机和配置Ubuntu这里就不再赘述了。 记得给Ubuntu配置VM tools1&#xff0c;方便把w…

软考中级(软件设计师)——面向对象程序设计(C++Java二选一的题15分-目标3分)

软考中级(软件设计师)——面向对象程序设计(C&Java二选一的题15分-目标3分) 目录 软考中级(软件设计师)——面向对象程序设计(C&Java二选一的题15分-目标3分) C语法要点 C类的定义&#xff1a; C派生类的定义&#xff1a; C类的外定义函数&#xff1a; C虚函数与…

Python Flask框架-开发简单博客-开篇介绍

作者&#xff1a;Eason_LYC 悲观者预言失败&#xff0c;十言九中。 乐观者创造奇迹&#xff0c;一次即可。 一个人的价值&#xff0c;在于他拥有的&#xff0c;而不是他会的。所以可以不学无数&#xff0c;但不能一无所有&#xff01; 技术领域&#xff1a;WEB安全、网络攻防 关…

PHP基础学习第十六篇(了解数组、创建数组、数组排序、总结数组的使用)

一、什么是数组 数组是一个能在单个变量中存储多个值的特殊变量。 如果有一个项目清单&#xff08;例如&#xff1a;序号名单&#xff09;&#xff0c;将其存储到单个变量中&#xff0c;如下所示&#xff1a; $a1;$b2;$c3; 然而&#xff0c;如果想要遍历数组并找出特定的一…

School assignment

目录 一、常用控制类汇编指令 二、编程实现统计寄存器AX中“1”和“0”的个数 三、编程实现从键盘输入10个1位整数 四、编程实现从键盘输入两个10进制的2位整数的和 五、编写程序练习直接、间接、相对、基址变址寻址 一、常用控制类汇编指令 MOV 传送字或字节. MOVSX 先…

JVM虚拟机

"天下事有难易乎&#xff1f;为之则易&#xff0c;不为则难" 一、初识JVM JVM是一个跨语言的平台&#xff0c;为那些能够跨平台运行的程序提供一个平台&#xff0c;JVM本身与java语言没有必然的联系&#xff0c;它只与特定的二进制文件格式.class文件所关联&#xff…

人生重开模拟器(Python实现)

文章目录人生重开模拟器介绍代码实现打印初始界面设置初始属性设置角色性别设置角色出生点针对每一岁&#xff0c;生成人生经历人生重开模拟器介绍 人生重开模拟器是由VickScarlet上传至GitHub的一款简单的文字网页游戏。 玩家点击“立即重开”并设置角色的初始属性后&#xf…

HTML 学习总结

超级详细的 HTML 学习笔记&#xff0c;一篇入门系列&#xff01;&#xff01;耐心读一遍 复习必备&#xff01;&#xff01; 目录 简要 认识标签 基本结构 常见标签 1. 注释 ---> ctrl / 2. 标题标签 ---> h1 h2 h3 h4 h5 h6 3. 段落标签 ---> p 4. 换行 ---> br …

Git | 一文带你零基础快速上手Git

&#x1f451; 博主简介&#xff1a;    &#x1f947; Java领域新星创作者    &#x1f947; 阿里云开发者社区专家博主、星级博主、技术博主 &#x1f91d; 交流社区&#xff1a;BoBooY&#xff08;优质编程学习笔记社区&#xff09; 前言&#xff1a;本文适合零基础小白…

Docker

目录1、docker介绍1.1 docker是什么1.2 容器与虚拟机的比较2、docker安装2.1 docker 基本组成2.2 安装步骤2.3 阿里云镜像加速2.4 run干了什么3、docker常用命令3.1 帮助命令及启动命令3.2 镜像命令3.3 容器命令4、Docker镜像4.1 镜像是什么&#xff1f;4.2 分层镜像 UnionFS 联…

时间复杂度和空间复杂度

文章目录一、算法的复杂度二、时间复杂度1.时间复杂度概念2.大O的渐进表示法3.常见时间复杂度计算举例3.1 实例1 【 O(N)】3.2 实例2 【O(NM)】3.3 实例3 【O(1)】3.4 实例4 【O(N)】3.5 实例5 【O(N^2)】3.6 实例6 【O(logN)】3.7 实例7 【O(N)】3.8 实例8【O(2^N)】三、空间复…

新的3D地图制图技术改变了全球定位的游戏规则

有人说&#xff1a;一个人从1岁活到80岁很平凡&#xff0c;但如果从80岁倒着活&#xff0c;那么一半以上的人都可能不凡。 生活没有捷径&#xff0c;我们踩过的坑都成为了生活的经验&#xff0c;这些经验越早知道&#xff0c;你要走的弯路就会越少。 在人类技术发展的历史长河…

市场裁猿~ 行业内卷~ Android 开发突破重围?

往日光鲜亮丽的互联网企业在今年彻底进入了寒冬&#xff0c;往年的高不可攀&#xff0c;低成本运营&#xff0c;在今年都不再是护身符。更是有不少互联网大厂开启了裁员模式&#xff0c;其中就有百度、阿里巴巴、快手、汽车之家等知名企业。 从今年三月起直到现在互联网各厂裁员…

【微信小程序系列:二】小程序常用功能:文字可复制、跳转地图、扫一扫、拨打电话、调整屏幕亮度、监听截屏...

一.先言&#xff1a; (&#xff5e;&#xffe3;▽&#xffe3;)&#xff5e;&#xff0c;hello&#xff0c;微信小程序系列第二篇&#xff0c;介绍下小程序里的常用功能api&#xff0c;可以快速copy使用~ 二.文字可复制&#xff1a; 小程序页面里的文字默认是没有长按复制功…

自动泊车停车位检测算法

转自&#xff1a;https://zhuanlan.zhihu.com/p/522630354 图1&#xff1a;泊车示意图一、背景介绍 自动泊车大体可分为4个等级&#xff1a; 第1级&#xff0c;APA 自动泊车&#xff1a;驾驶员在车内&#xff0c;随时准备制动&#xff0c;分为雷达感知和雷达视觉感知两种方式。…