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

news/2024/9/13 14:25:26

剑指 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];
            else
                break;
        }
        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;
            else
                node = 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://lihuaxi.xjx100.cn/news/1931428.html

相关文章

软件项目概要设计说明书(直接套用)

1引言 1.1编写目的 1.2项目背景 1.3参考资料 2系统总体设计 2.1整体架构 2.2整体功能架构 2.3整体技术架构 2.4运行环境设计 2.5设计目标 3系统功能模块设计 3.1个人办公 3.2系统管理 4性能设计 4.1响应时间 4.2并发用户数 5接口设计 5.1接口设计原则 5.2接口…

【elfboard linux开发板】7.i2C工具应用与aht20温湿度寄存器读取

1. I2C工具查看aht20的温湿度寄存器值 1.1 原理图 传感器通过IIC方式进行通信&#xff0c;连接的为IIC1总线&#xff0c;且设备地址为0x38&#xff0c;实际上通过后续iic工具查询&#xff0c;这个设备是挂载在iic-0上 1.2 I2C工具 通过i2c工具可以实现查询i2c总线、以及上面…

Nacos、OpenFeign、Ribbon、loadbalancer组件工作的原理

Nacos、OpenFeign、Ribbon、loadbalancer组件工作的原理 Nacos是什么&#xff0c;官网中有这么一段话 这一段话说的直白点就是Nacos是一个注册中心和配置中心&#xff01; 在Nacos中有客户端和服务端的这个概念 服务端需要单独部署&#xff0c;用来保存服务实例数据的 客户端…

使用定时器setInterval,在Moment.js 时间格式化插件基础上完成当前时间持续动态变化

1、引入插件 npm install moment --save 2、js配置&#xff1a;当前需要使用的文件中直接引入 import moment from moment; 3、代码实现&#xff1a;定义一个变量进行回显 3.1、dom部分 <span> {{ timeData }} </span> 3.2、js代码 <script> import mo…

uView注意事项

由于uni-app支持多端开发&#xff0c;而各端&#xff0c;特别是各小程序平台&#xff0c;没有统一的标准&#xff0c;加重了开发者和企业的成本&#xff0c;幸好uni-app使用Vue标准&#xff0c;对各端进行了写法的统一&#xff0c; 推动了生态的发展&#xff0c;但是由于某些小…

探索 PyTorch 中的 torch.nn 模块(2)

目录 torch.nn模块详解 register_module_forward_pre_hook 主要特性和用途 警告 钩子签名 使用方法 返回值 示例代码 register_module_forward_hook 主要特性和用途 警告 钩子签名 使用方法 参数 返回值 示例代码 register_module_backward_hook 主要用途 …

【JavaWeb后端开发-第一章】Maven

文章目录 1. 介绍1.1. 什么是Maven1.2. Maven的作用 2. Maven概述2.1. Maven介绍2.2. Maven模型2.3. Maven仓库2.4. Maven安装2.4.1. 下载2.4.2. 安装步骤 3. IDEA集成Maven3.1. 配置Maven环境3.1.1. 当前工程配置3.1.2. 全局配置 3.2. Maven 项目3.2.1. 创建Maven项目3.2.2. P…

【PTA-C语言】实验八-函数与指针II

如果代码存在问题&#xff0c;麻烦大家指正 ~ ~有帮助麻烦点个赞 ~ ~ 目录——实验八-函数与指针II 6-1 移动字母&#xff08;分数 10&#xff09;6-2 删除字符&#xff08;分数 10&#xff09;6-3 函数实现字符串逆序&#xff08;分数 10&#xff09;6-4 递归计算Ackermenn函数…