剑指 Offer(第2版)面试题 68:树中两个结点的最低公共祖先

剑指 Offer(第2版)面试题 68:树中两个结点的最低公共祖先

  • 剑指 Offer(第2版)面试题 68:树中两个结点的最低公共祖先
    • 解法1:递归
    • 拓展题:二叉搜索树的最近公共祖先
      • 解法1:两次遍历
      • 解法2:一次遍历

剑指 Offer(第2版)面试题 68:树中两个结点的最低公共祖先

题目来源:88. 树中两个结点的最低公共祖先

题目描述:给出一个二叉树,输入两个树节点,求它们的最低公共祖先。一个树节点的祖先节点包括它本身。

解法1:递归

祖先的定义: 若节点 p 在节点 root 的左(右)子树中,或 p=root,则称 root 是 p 的祖先。

最近公共祖先的定义: 设节点 root 为节点 p、q 的某公共祖先,若其左子节点 root.left 和右子节点 root.right 都不是 p、q 的公共祖先,则称 root 是“最近的公共祖先”。

根据以上定义,若 root 是 p、q 的 最近公共祖先 ,则只可能为以下情况之一:

  1. p 和 q 在 root 的子树中,且分列 root 的异侧(即分别在左、右子树中);
  2. p=root,且 qqq 在 root 的左或右子树中;
  3. q=root,且 ppp 在 root 的左或右子树中;

考虑通过递归对二叉树进行先序遍历,当遇到节点 p 或 q 时返回。从底至顶回溯,当节点 p、q 在节点 root 的异侧时,节点 root 即为最近公共祖先,则向上返回 root。

在这里插入图片描述

代码:

/*** 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:TreeNode *lowestCommonAncestor(TreeNode *root, TreeNode *p, TreeNode *q){if (root == nullptr)return root;// p 和 q 其中有一个正好是 root,直接返回 root 就行if (root == p || root == q)return root;// 通过递归,得到左右两棵子树的值TreeNode *leftLCA = lowestCommonAncestor(root->left, p, q);TreeNode *rightLCA = lowestCommonAncestor(root->right, p, q);// p 和 q 分别在 root 的不同子树,直接返回 root 就行if (leftLCA && rightLCA)return root;// p 和 q 在 root 的同一侧,且 root 不等于 p 或者 q 的任何一个,那么就找 p 和 q 在的那一侧子树return leftLCA == nullptr ? rightLCA : leftLCA;}
};

复杂度分析:

时间复杂度:O(n),其中 n 是二叉树的节点个数。最差情况下,需要递归遍历树的所有节点。

空间复杂度:O(height),其中 height 是二叉树的深度。

拓展题:二叉搜索树的最近公共祖先

题目链接:235. 二叉搜索树的最近公共祖先

解法1:两次遍历

分别找到 root 到 p 和 root 到 p 的路径,因为 root 到 p 和 q 的最近公共祖先的路径长度一样,所以比较 path_p[i] 和 path_q[i] 即可,相等就说明找到了 p 和 q 的最近公共祖先。

代码:

// 两次遍历class Solution
{
public:// 主函数TreeNode *lowestCommonAncestor(TreeNode *root, TreeNode *p, TreeNode *q){TreeNode *ancestor = nullptr;vector<TreeNode *> path_p = getPath(root, p);vector<TreeNode *> path_q = getPath(root, q);for (int i = 0; i < path_p.size() && i < path_q.size(); i++){if (path_p[i] == path_q[i])ancestor = path_p[i];elsebreak;}return ancestor;}// 辅函数 - 得到从根节点到目标节点的路径vector<TreeNode *> getPath(TreeNode *root, TreeNode *target){vector<TreeNode *> path;TreeNode *node = root;while (node != target){path.push_back(node);if (node->val > target->val)node = node->left;elsenode = node->right;}path.push_back(node);return path;}
};

复杂度分析:

时间复杂度:O(n),其中 n 是给定的二叉搜索树中的节点个数。

空间复杂度:O(n),其中 n 是给定的二叉搜索树中的节点个数。我们需要存储根节点到 p 和 q 的路径。

解法2:一次遍历

利用二叉搜索树的特性,只需要一次遍历。

我们从根节点开始遍历:

  1. 如果当前节点的值大于 p 和 q 的值,说明 p 和 q 应该在当前节点的左子树,因此将当前节点移动到它的左子节点;
  2. 如果当前节点的值小于 p 和 q 的值,说明 p 和 q 应该在当前节点的右子树,因此将当前节点移动到它的右子节点;
  3. 如果当前节点的值不满足上述两条要求,那么说明当前节点就是「分岔点」。此时,p 和 q 要么在当前节点的不同的子树中,要么其中一个就是当前节点。

代码:

// 一次遍历class Solution
{
public:TreeNode *lowestCommonAncestor(TreeNode *root, TreeNode *p, TreeNode *q){TreeNode *ancestor = root;while (1){// 如果当前节点的值大于 p 和 q 的值,说明 p 和 q 在当前节点的左子树if (ancestor->val > p->val && ancestor->val > q->val)ancestor = ancestor->left;// 如果当前节点的值小于p和q的值,说明p和q在当前节点的右子树else if (ancestor->val < p->val && ancestor->val < q->val)ancestor = ancestor->right;else // 当前节点就是「分岔点」break;}return ancestor;}
};

复杂度分析:

时间复杂度:O(n),其中 n 是给定的二叉搜索树中的节点个数。

空间复杂度:O(1)。

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

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

相关文章

java面向对象构造器--学习笔记

什麽是构造器&#xff1f; 构造器就是一种特殊的方法&#xff0c;特殊在&#xff1a; 方法名 类名不能写返回值类 只要声明了这么一个特殊的方法&#xff0c;那么这个方法就不叫方法&#xff0c;叫做构造器了 构造器有什么特点&#xff1f; 创建对象时&#xff0c;对象会去…

STM32 学习(二)GPIO

目录 一、GPIO 简介 1.1 GPIO 基本结构 1.2 GPIO 位结构 1.3 GPIO 工作模式 二、GPIO 输出 三、GPIO 输入 1.1 传感器模块 1.2 开关 一、GPIO 简介 GPIO&#xff08;General Purpose Input Output&#xff09;即通用输入输出口。 1.1 GPIO 基本结构 如下图&#xff0…

【数据分析】指数移动平均线的直观解释

slavahead 一、介绍 在时间序列分析中&#xff0c;通常需要通过考虑先前的值来了解序列的趋势方向。序列中下一个值的近似可以通过多种方式执行&#xff0c;包括使用简单基线或构建高级机器学习模型。 指数&#xff08;加权&#xff09;移动平均线是这两种方法之间的稳健权衡。…

阿里云服务器端口PPTP 1723放行教程

阿里云服务器安装PPTP VPN需要先开通1723端口&#xff0c;阿里云服务器端口是在安全组中操作的&#xff0c;阿里云服务器网aliyunfuwuqi.com来详细说明阿里云服务器安全组开放PPTP VPN专用1723端口教程&#xff1a; 阿里云服务器放行1723端口教程 PPTP是点对点隧道协议&#…

fastadmin传递参数给html和js,通过身份判断动态显示列表头部住店和离店按钮

首先将管理员或者酒店人员的身份传递给html和js做按钮显示权限 roomorder.php index.html {if $admin_id != 1}<a class="btn btn-success btn-change btn-start btn-disabled" data-params=

2023机器人行业总结,2024机器人崛起元年(具身智能)

2023总结&#xff1a; 1.Chatgpt引爆了通用人工智能&#xff0c;最大的受益者或是机器人&#xff0c;2023年最热门的创业赛道便是人形机器人&#xff0c;优必选更是成为人形机器人上市第一股&#xff0c; 可以说2023年是机器人开启智能化的元年&#xff0c;而2024则将成为机器…

Java解析xml文档,判断对象是一个json是jsonArray还是jsonObject

有一篇xml文档&#xff0c;如下&#xff1a; 现在需要解析出其中的内容&#xff0c;首先需要明确的是&#xff0c;文档是由一个个的标签嵌套形成的&#xff0c;例如整个xml文件是由许多DescriptorRecord标签构成&#xff0c; <DescriptorRecord DescriptorClass "1&…

rk3588中编译带有ffmpeg的opencv

有朋友有工程需要&#xff0c;将视频写成mp4&#xff0c;当然最简单的方法当然是使用opencv的命令 cv::VideoWriter writer;bool bRet writer.open("./out.mp4", cv::VideoWriter::fourcc(m, p, 4, v), 15, cv::Size(640, 512), 1); 但是奈何很难编译成功&#xff…

MT8766安卓核心板规格参数_MTK8766核心板模块方案定制

MT8766安卓核心板&#xff1a;高性能、稳定可靠、集成度高的一体化解决方案 MT8766安卓核心板采用联发科MTK8766四核4G模块方案&#xff0c;是一款高度集成的安卓一体板。四核芯片架构&#xff0c;主频可达到2.0GHz&#xff0c;支持国内4G全网通。12nm制程工艺&#xff0c;支持…

【运维】yarn高可用配置详解

文章目录 一. 架构1. RM故障转移( Failover)1.1. 手动切换故障转移1.2. 自动故障转移1.3. RM故障转移的客户端&#xff1a;ApplicationMaster和NodeManager 2. 恢复以前的活动RM的状态 二、部署方式1. 配置说明2. 配置实例3. Admin commandsa. 检查状态b. 手动切换主备 本文主要…

VS2017 搭建opencv工程

VS2017 搭建opencv工程 opencv在处理图像方面具有很强的能力&#xff0c;在使用opencv之前先需要造好轮子。 1、opencv 官网 &#xff0c;下载对应的资源文件包。 根据自身选择。下载包之后&#xff0c;解压。分为build和sources source目录下分别存放&#xff1a; modules: …

Mysqld的关键优化参数

skip-name-resolve 现象 mysql连接很慢&#xff0c;登陆到服务器上查看服务器日志都是正常的&#xff0c;无可疑记录&#xff0c;登陆到mysql服务器上&#xff0c;查看下进程&#xff0c;发现有很多这样的连接&#xff1a; 218 | unauthenticated user | 192.168.10.6:44500 |…