二叉树28:二叉搜索树的最近公共祖先

news2024/9/20 8:55:46

主要是我自己刷题的一些记录过程。如果有错可以指出哦,大家一起进步。
转载代码随想录
原文链接:
代码随想录
leetcode链接:235. 二叉搜索树的最近公共祖先

题目:

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

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

例如,给定如下二叉搜索树: root = [6,2,8,0,4,7,9,null,null,3,5]
在这里插入图片描述

示例:

示例 1:

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

示例 2:

输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4
输出: 2
解释: 节点 2 和节点 4 的最近公共祖先是 2, 因为根据定义最近公共祖先节点可以为节点本身

提示:

所有节点的值都是唯一的。
p、q 为不同节点且均存在于给定的二叉搜索树中。

思路:

做过二叉树:公共祖先问题题目的同学应该知道,利用回溯从底向上搜索,遇到一个节点的左子树里有p,右子树里有q,那么当前节点就是最近公共祖先。

那么本题是二叉搜索树,二叉搜索树是有序的,那得好好利用一下这个特点。

在有序树里,如果判断一个节点的左子树里有p,右子树里有q呢?

因为是有序树,所有 如果 中间节点是 q 和 p 的公共祖先,那么 中节点的数组 一定是在 [p, q]区间的。即 中节点 > p && 中节点 < q 或者 中节点 > q && 中节点 < p。

那么只要从上到下去遍历,遇到 cur节点是数值在[p, q]区间中则一定可以说明该节点cur就是q 和 p的公共祖先。 那问题来了,一定是最近公共祖先吗

如图,我们从根节点搜索,第一次遇到 cur节点是数值在[p, q]区间中,即 节点5,此时可以说明 p 和 q 一定分别存在于 节点 5的左子树,和右子树中。
在这里插入图片描述此时节点5是不是最近公共祖先? 如果 从节点5继续向左遍历,那么将错过成为q的祖先, 如果从节点5继续向右遍历则错过成为p的祖先。

所以当我们从上向下去递归遍历,第一次遇到 cur节点是数值在[p, q]区间(闭区间)中,那么cur就是 p和q的最近公共祖先

理解这一点,本题就很好解了。

而递归遍历顺序,本题就不涉及到 前中后序了(这里没有中节点的处理逻辑,遍历顺序无所谓了)。

如图所示:p为节点6,q为节点9
在这里插入图片描述
可以看出直接按照指定的方向,就可以找到节点8,为最近公共祖先,而且不需要遍历整棵树,找到结果直接返回!

递归法

递归三部曲如下:

1.确定递归函数返回值以及参数

参数就是当前节点,以及两个结点 p、q。

返回值是要返回最近公共祖先,所以是TreeNode * 。

代码如下:

TreeNode* traversal(TreeNode* cur, TreeNode* p, TreeNode* q)

2.确定终止条件

遇到空返回就可以了,代码如下:

if (cur == NULL) return cur;

其实都不需要这个终止条件,因为题目中说了p、q 为不同节点且均存在于给定的二叉搜索树中。也就是说一定会找到公共祖先的,所以并不存在遇到空的情况。

3.确定单层递归的逻辑

在遍历二叉搜索树的时候就是寻找区间[p->val, q->val](注意这里是左闭又闭)

那么如果 cur->val 大于 p->val,同时 cur->val 大于q->val,那么就应该向左遍历(说明目标区间在左子树上)。

需要注意的是此时不知道p和q谁大,所以两个都要判断

代码如下:

if (cur->val > p->val && cur->val > q->val) {
    TreeNode* left = traversal(cur->left, p, q);
    if (left != NULL) {
        return left;
    }
}

细心的同学会发现,在这里调用递归函数的地方,把递归函数的返回值left,直接return。

在二叉树:公共祖先问题中,如果递归函数有返回值,如何区分要搜索一条边,还是搜索整个树。

搜索一条边的写法:

if (递归函数(root->left)) return ;
if (递归函数(root->right)) return ;

搜索整个树写法:

left = 递归函数(root->left);
right = 递归函数(root->right);
left与right的逻辑处理;

本题就是标准的搜索一条边的写法,遇到递归函数的返回值,如果不为空,立刻返回。

如果 cur->val 小于 p->val,同时 cur->val 小于 q->val,那么就应该向右遍历(目标区间在右子树)。

if (cur->val < p->val && cur->val < q->val) {
    TreeNode* right = traversal(cur->right, p, q);
    if (right != NULL) {
        return right;
    }
}

剩下的情况,就是cur节点在区间(p->val <= cur->val && cur->val <= q->val)或者 (q->val <= cur->val && cur->val <= p->val)中,那么cur就是最近公共祖先了,直接返回cur。

代码如下:

return cur;

那么整体递归代码如下:

class Solution {
private:
    TreeNode* traversal(TreeNode* cur, TreeNode* p, TreeNode* q) {
        if (cur == NULL) return cur;
                                                        // 中
        if (cur->val > p->val && cur->val > q->val) {   // 左
            TreeNode* left = traversal(cur->left, p, q);
            if (left != NULL) {
                return left;
            }
        }

        if (cur->val < p->val && cur->val < q->val) {   // 右
            TreeNode* right = traversal(cur->right, p, q);
            if (right != NULL) {
                return right;
            }
        }
        return cur;
    }
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        return traversal(root, p, q);
    }
};

精简后代码如下:

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if (root->val > p->val && root->val > q->val) {
            return lowestCommonAncestor(root->left, p, q);
        } else if (root->val < p->val && root->val < q->val) {
            return lowestCommonAncestor(root->right, p, q);
        } else return root;
    }
};

迭代法

对于二叉搜索树的迭代法,大利用其有序性,迭代的方式还是比较简单的,解题思路在递归中已经分析了。

迭代代码如下:

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        while(root) {
            if (root->val > p->val && root->val > q->val) {
                root = root->left;
            } else if (root->val < p->val && root->val < q->val) {
                root = root->right;
            } else return root;
        }
        return NULL;
    }
};

总结

对于二叉搜索树的最近祖先问题,其实要比普通二叉树公共祖先问题简单的多。

不用使用回溯,二叉搜索树自带方向性,可以方便的从上向下查找目标区间,遇到目标区间内的节点,直接返回。

最后给出了对应的迭代法,二叉搜索树的迭代法甚至比递归更容易理解,也是因为其有序性(自带方向性),按照目标区间找就行了。

自己的代码

利用了这个p、q 为不同节点且均存在于给定的二叉搜索树中。省了很多步骤。

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if ((q->val <= root->val && root->val <= p->val)||
			(p->val <= root->val && root->val <= q->val))	return root;
		if (q->val < root->val &&  p->val< root->val)	return lowestCommonAncestor(root->left,p,q);
		if (root->val < q->val && root->val < p->val)	return lowestCommonAncestor(root->right, p, q);
        return nullptr;
    }
};



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

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

相关文章

对于Go 语言的进阶与依赖管理| 青训营笔记

一.Go 语言进阶与依赖管理 1.1并发和并行 Go可以充分发挥多核优势&#xff0c;高效运行。 多线程程序在单核心的 cpu 上运行&#xff0c;称为并发&#xff1b; 多线程程序在多核心的 cpu 上运行&#xff0c;称为并行。 并发与并行并不相同&#xff0c;并发主要由切换时间片…

2016年专业408算法题

文章目录0 结果1 题目2 思路2.1 思路1&#xff08;较优解&#xff1a;排序&#xff09;2.2 思路2&#xff08;最优解&#xff1a;类快排思想排序&#xff09;附录0 结果 较优解&#xff1a; 最优解&#xff1a; 1 题目 2 思路 为了使&#xff5c;n1−n2&#xff5c;&#…

1.2.3存储结构:主存编址计算、主存编址的过程、存储单元、编址内容、存储总容量

1.2.3存储结构&#xff1a;主存编址计算、主存编址的过程、存储单元、编址内容、存储总容量主存编址的过程存储单元主存编址存储单元编址内容存储总容量例题主存编址的过程 计算机是一个机器&#xff0c;它能够识别的是机器语言&#xff0c;电器信号。因此计算机当中所有的数据…

英语学习 2

1 词汇积累 1、imply and infer 暗示和推断 2、indicate 显示、指出 3、outgoing 外向的 4、sympathy 同情心 5、sympathetic 有同情心的 6、evolution 进化 8、agreement 一致 10、resourceful 足智多谋的 11、appear 似乎 12、manufacturers 厂家、制造商 13、toilet paper …

linux定时器crond使用方式简介

文章目录一、简介二、cron.d下文件示例三、被调用的脚本文件四、检查脚本是否执行五、遇到的脚本未执行的情况一、简介 一般来说在/etc目录下&#xff0c;有5个以cron开头的目录&#xff0c;分别是/etc/cron.hourly&#xff0c;/etc/cron.daily&#xff0c;/etc/cron.weekly&a…

java泛型6

到底何时使用泛型方法&#xff1f;何时使用类型通配符呢&#xff1f;大多数时候都可以使用泛型方法来代替类型通配符。 这种场景下效果一样。 上面方法使用了泛型形式&#xff0c;这时定义泛型形参时设定上限&#xff08;其中E是Collection接口里定义的泛型&#xff0c;在该接…

搭建企业知识库的意义

当客户跟你达成合作关系后&#xff0c;需要持续的关系维护&#xff0c;在一定的销售点&#xff0c;定期和客户沟通&#xff0c;据调查&#xff0c;赢得一个新客户的成本可能是保留一个现有客户的5到25倍&#xff0c;作为营销策略&#xff0c;客户服务支持必须满足他们的期望。建…

Java---微服务---Seata的部署和集成

Seata的部署和集成一、部署Seata的tc-server1.下载2.解压3.修改配置4.在nacos添加配置5.创建数据库表6.启动TC服务二、微服务集成seata1.引入依赖2.修改配置文件三、TC服务的高可用和异地容灾1.模拟异地容灾的TC集群2.将事务组映射配置到nacos3.微服务读取nacos配置一、部署Sea…

PEM格式RSA密钥解析(二)

PEM格式RSA密钥解析&#xff08;二&#xff09; RSA密钥参数解析 上一部分讲解了将Base64编码的密钥数据转换成hex格式数据&#xff0c;本章将介绍如何获从转码后的数据中获取RSA密钥的相关参数。 根据 RSA 密钥语法中的结构对私钥解析结果如下&#xff1a; 上一节转码后的私…

暗月ACK靶场 WP

环境搭建 https://mp.weixin.qq.com/s/VB4elHdrHNCmPDP_ktcLRg https://www.bilibili.com/video/BV1264y187St?spm_id_from333.1007.top_right_bar_window_history.content.click 按照文章拓扑根据实际情况搭建好&#xff0c;web2的其中一个网卡需要自己调一下ip 1、把 12s…

第五届字节跳动青训营 前端进阶学习笔记(十)Webpack基础

文章目录前言什么是Webpack1.Webpack概述2.Webpack基本使用Webpack打包核心流程1.webpack需要做的事情2.Webpack的使用3.处理CSS4.接入babel5.生成HTML6.HMR7.Tree-ShakingLoader1.Loader的主要特性2.常见Loader总结前言 课程重点&#xff1a; 什么是WebpackWebpack打包核心流…

本地启动打包后文件

本地启动打包后文件在 vs code 扩展中安装 Live Server配置 Live Server在 vs code 扩展中安装 Live Server 点击安装 Live Server 配置 Live Server VS Code setting.json 中配置 Vue/React 打包后文件夹 build/dist 为服务器的根目录 "liveServer.settings.root"…

AOP实例 – 环绕增强 日志记录

AOP实例 – 环绕增强 日志记录需求&#xff1a;记录图书的service层的日志操作&#xff0c;到数据库1. 创建商品日志记录表 Book_Log2. 完成日志记录表的插入逻辑3. 环绕增强搜集日志记录参数&#xff0c;并测试4. 环绕增强调用日志记录表的插入逻辑&#xff0c;完成日志记录功…

【目标检测论文解读复现NO.29】基于YOLO-ST的安全帽佩戴精确检测算法

前言此前出了目标改进算法专栏&#xff0c;但是对于应用于什么场景&#xff0c;需要什么改进方法对应与自己的应用场景有效果&#xff0c;并且多少改进点能发什么水平的文章&#xff0c;为解决大家的困惑&#xff0c;此系列文章旨在给大家解读最新目标检测算法论文&#xff0c;…

Spire.Barcode 5.1.0 for Java Patch

Spire.Barcode for Java是专门为开发人员设计的专业条码组件&#xff0c;用于在 Java 应用程序&#xff08;J2SE 和 J2EE&#xff09;上生成、读取和扫描 一维和二维条码。开发人员和程序员可以使用 Spire.Barcode 快速轻松地将企业级条码格式添加到他们的 Java 应用程序中。需…

【数字逻辑】逻辑函数式化简为其他形式

以 F&#xff08;A,B,C&#xff09;ABA‾CF&#xff08;A,B,C&#xff09; AB\overline{\rm A}CF&#xff08;A,B,C&#xff09;ABAC 为例&#xff0c;说明如何将与或式转换为其它类型的表达式。

CE认证和CCC认证区别?

CE认证和CCC认证区别? 参考链接:https://baijiahao.baidu.com/s?id=1728784934635704528&wfr=spider&for=pc CE认证和CCC认证有什么区别? 我发现我的很多朋友对此并不了解。 同时我发现很多产品在很多产品包装上都通过了CCC和CE认证,所以可能会引起没有做过产品…

硬件 -CPU工作原理

1.地址空间一个处理器能够访问&#xff08;读写&#xff09;的存储空间有限&#xff0c;我们称空间为地址空间&#xff0c;一般来说N位地址总线的处理器的地址空间是2的N次方。CPU从内存中取数据&#xff0c;先发地址&#xff0c;内存收到后&#xff0c;发送地址所在的数据。2.…

蓝奥声无线同步数据传输技术在物联网方面应用的优势

随着物联网技术的发展&#xff0c;其应用场景已经从简单的商品和服务扩展到广泛的社会和行业。当前&#xff0c;世界各地都在努力通过技术推动经济增长。对于许多行业来说&#xff0c;物联网技术的应用无疑将是一个新的增长机会。同时&#xff0c;物联网将推动社会结构从“制造…

Red Hat Linux 命令Crontab的使用方法

Red Hat Linux 命令Crontab的使用方法1 cron是一个linux下的定时执行工具&#xff0c;可以在无需人工干预的情况下运行作业。由于Cron 是Linux的内置服务&#xff0c;但它不自动起来&#xff0c;可以用以下的方法启动、关闭这个服务: /sbin/service crond start //启动服务 /…