算法力扣刷题记录 五十七【236. 二叉树的最近公共祖先】和【235. 二叉搜索树的最近公共祖先】

前言

公共祖先解决。二叉树和二叉搜索树条件下的最近公共祖先。
二叉树篇继续。


一、【236. 二叉树的最近公共祖先】题目阅读

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

百度百科中最近公共祖先的定义为:“对于有根树 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, 10^5] 内。
-10^9 <= Node.val <= 10^9
所有 Node.val 互不相同 。
p != q
p 和 q 均存在于给定的二叉树中。

二、【236. 二叉树的最近公共祖先】题目解答

弯路(第一次做题时,可能的想法)

  1. 二叉树习惯用递归法解决问题,那么需要确定遍历顺序重复执行的递归逻辑
  2. 本题是二叉树,需要求最近公共祖先,那么父节点应该是最后遍历。所以尝试遍历顺序:后序——左右中。
  3. 如何找到统一执行的规律呢?回到中间节点需要处理什么逻辑?
  • 受前几节二叉搜索树中序遍历整合到数组中,那么此处遍历结果数组得到之后,看p,q哪个在前,选择在前面的p或者q?可是无法区分自己是祖先的情况。(不正确)

正确思路获取

参考思路链接

  1. 确定后序遍历,因为需要从节点p,q向上退出父节点,所以中间节点最后处理。(左右中)
  2. 返回中间节点什么信息?判断左子树中是否存在p或q,判断右子树中是否存在p或q:
  • 如果左子树有p,右子树有q;左子树有q,右子树有p。此时返回中间节点(最近公共祖先)。

  • 如果一边子树为空,另一边子树有pq之一。此时返回left或者right,即子树返回的节点。注意:这里不能返回中间节点。会把最近公共祖先丢掉。
    在这里插入图片描述

  • 如果两边子树都为空,说明该中间节点的左右子树不包含pq。向上返回空。

  1. 所以递归函数返回值:TreeNode* 类型;
  2. 递归函数终止条件:如果cur是空,return空。如果cur->val是pq,return cur。
  • 两个终止条件:第一个空判断可以容易想到。
  • 如果第二个终止条件没有想到:影响吗?可以拯救:在中间逻辑先判断该节点是否为p或q,如果是,那么return cur;如果不是,走第2.点的逻辑。
  • 所以第二个终止条件放到中间节点之前,更简单。
  1. 总结:左右中:先看左右子树能向中间节点返回什么信息——空节点说明不包含p和q;非空节点说明有p或有q或有p和q。注意,中间节点应该真正返回什么,有的是cur(自身),有的是left或right.

代码实现【二叉树的最近公共祖先+递归法】

class Solution {
public:TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {if(!root) return nullptr;if(root->val == q->val || root->val == p->val) return root;//左TreeNode* left = lowestCommonAncestor(root->left,p,q);//右TreeNode* right = lowestCommonAncestor(root->right,p,q);//中节点if(!left && !right) return nullptr;//说明该子树没有p也没有qelse if(!left && right) return right;//一定要返回right。而不是rootelse if(!right && left) return left;//一定要返回left。而不是root。else return root;}
};

补充说明【细节】

补充思路中没提及的情况2:自身是祖先——以上代码在哪一处包含

  1. p和q不是包含关系,情况1:肯定是某个中间节点的两边子树;
  2. p和q是包含关系,无论谁包含谁——情况2:如示例2.在某个节点的同一个子树内。if(root->val == q->val || root->val == p->val) return root;将包含者向上返回。(不用深入找q了。)
    在这里插入图片描述

拯救:没想到终止条件2时

  • 可以这样想:先看自己是不是目标,如果是,返回自己。如果不是,看左右子树有没有包含。
  • 看left和right之前先确定自己不是目标。else return root。
  • 所以把终止条件2放到上面更好一点。
class Solution {
public:TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {if(!root) return nullptr;//没想到在这里直接终止。//if(root->val == q->val || root->val == p->val) return root;//左TreeNode* left = lowestCommonAncestor(root->left,p,q);//右TreeNode* right = lowestCommonAncestor(root->right,p,q);//中节点if(root->val == p->val || root->val == q->val){return root;}else{if(!left && !right) return nullptr;//说明该子树没有p也没有qelse if(!left && right) return right;//一定要返回right。而不是rootelse if(!right && left) return left;//一定要返回left。而不是root。else return root;}}
};

理解提示给节点值不重复、p和q不相等、p 和 q 均存在于给定的二叉树中

  • 这个提示有用。值不相等,说明p是唯一的节点,没有p1,p2,p3……,return的一定是节点p;q同理。
  • p和q不相等,且都在二叉树中,说明:
    • 情况一:p和q非包含关系。一左一右,说明left和right一个遇到p,一个遇到q。
    • 情况二:p和q包含关系。说明left或right先遇到其中之一。

三、【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 为不同节点且均存在于给定的二叉搜索树中。

四、【235. 二叉搜索树的最近公共祖先】题目解答

4.1 尝试解答

4.1.1思路【个人】

如果当作普通二叉树,那么上一道题肯定能解决。那么此处就要利用二叉搜索树的性质。

  1. 二叉搜索树最大的特点:左子树 < 中间节点 < 右子树,数值能有序整理这可以给搜索指明方向,就不用搜索整个树
  2. 节点p和节点q,假设p->val < q->val,始终坚持这个原则,如果不是,就要换数据。
  3. 大小关系:
  • 如果cur->val > q->val,说明应该遍历左子树,右子树不用遍历,得到左子树的返回之后,直接return left。
  • 如果cur->val < p->val ,说明应该遍历右子树,左子树不用遍历,得到右子树的返回之后,直接return right。
  • 如果p->val < cur->val < q->val,当前节点的值在中间,说明当前节点就是公共祖先
  • 可以画图辅助理解。如下:
    在这里插入图片描述
  1. 递归函数终止条件:上面已经说了两点终止条件。自然再加上cur为空的情况。
  2. 递归函数返回值:返回节点类型。

4.1.2代码实现【递归】

/*** 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:void swap(TreeNode*& p, TreeNode*& q){if(p->val > q->val){//设定q的值大于p的值。TreeNode* temp = p;p = q;q = temp;}return;}TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {//先保证p的值小于q的值swap(p,q);//终止条件1:空节点返回空if(!root) return nullptr;//终止条件2:中间节点的值在p和q中间,或自身祖先。if( (p->val < root->val && root->val < q->val) || root->val == p->val || root->val == q->val) return root;if(root->val > q->val){TreeNode* left = lowestCommonAncestor(root->left,p,q);//进到左子树return left;//此处会直接返回。没有遍历整个树。}else if(root->val < p->val){TreeNode* right = lowestCommonAncestor(root->right,p,q);//进到右子树return right;}return nullptr;//走不到这。}
};

4.2 【235. 二叉搜索树的最近公共祖先】参考学习

参考学习链接

学习内容

  1. 思路大体一致,但是代码还有改进点:
  • 判断条件的处理上:中间root->val比p和q的值都大,那么遍历左子树;中间root->val比p和q的值都小,那么遍历右子树。剩下就是root->val在中间,直接返回即可。
  • 因此:不需要swap(p,q) 来始终保持q的值比p大;但是第一次做,这是最直观,方便分析的思路。
  1. 所以:代码改进【递归法】
class Solution {
public:TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {//终止条件:空节点返回空if(!root) return nullptr;if(root->val > q->val && root->val > p->val){TreeNode* left = lowestCommonAncestor(root->left,p,q);//进到左子树//没有加left为空的判断,因为题目说p和q存在。严格来说加上更通用。if(left) return left;//此处会直接返回。没有遍历整个树。}else if(root->val < p->val && root-=>val < q->val){TreeNode* right = lowestCommonAncestor(root->right,p,q);//进到右子树if(right) return right;}//中return root;//剩余情况。}
};
  1. 迭代法:思路肯定一样。尝试实现下:
class Solution {
public:TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {queue<TreeNode*> que;if(!root) return nullptr;que.push(root);while(!que.empty()){TreeNode* cur = que.front();que.pop();if(cur->val > p->val && cur->val > q->val && cur->left){que.push(cur->left);}else if(cur->val < p->val && cur->val < q->val && cur->right){que.push(cur->right);}else return cur;}return nullptr;//能找到在上一行会直接return,不会走到这一行。}
};

对比参考代码:上面还是有点复杂——用了队列,不过这是迭代模版,肯定能实现。
参考给出:直接用root指针交接下一棒。


总结

在这里插入图片描述
(欢迎指正,转载标明出处)

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

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

相关文章

Windows 磁盘分区样式有几种?如何查看电脑分区样式?

在使用 Windows 操作系统的过程中&#xff0c;磁盘分区是一个重要的概念。磁盘分区的方式直接影响到数据存储和系统运行的效率。磁盘分区的时候也有不同的样式&#xff0c;你知道分区类型有哪些吗&#xff1f;不同的分区样式决定了硬盘的分区方式、可支持的最大存储容量以及兼容…

学习笔记:MySQL数据库操作3

1. 创建数据库和表 创建数据库 mydb11_stu 并使用该数据库。创建 student 表&#xff0c;包含字段&#xff1a;学号&#xff08;主键&#xff0c;唯一&#xff09;&#xff0c;姓名&#xff0c;性别&#xff0c;出生年份&#xff0c;系别&#xff0c;地址。创建 score 表&…

Etsy:以手工制品和复古商品闻名的美国淘宝允许AI艺术品售卖

Etsy是一个美国网络商店平台&#xff0c;以手工艺成品买卖为主要特色&#xff0c;曾被纽约时报拿来和eBay&#xff0c;Amazon比较&#xff0c;被誉为“祖母的地下室收藏”。 Etsy 是一家以手工制品和复古商品闻名的美国网络商店平台在线市场&#xff0c;以手工艺成品买卖为主要…

由“微软蓝屏”事件引发的对网络安全与系统稳定性的思考

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、软件更新流程的漏洞与改进二、强化应急响应机制三、技术创新与应用四、关键行业的特殊应对五、用户意识的提升与数据备份六、全球合作与统一标准总结 前言 …

浅谈断言之XML断言

浅谈断言之XML断言 XML断言是JMeter的一个组件&#xff0c;用于验证请求的响应数据是否符合XML结构。这对于测试返回XML格式数据的Web服务特别有用。 如何添加XML断言&#xff1f; 要在JMeter测试计划中添加XML断言&#xff0c;遵循以下步骤&#xff1a; 打开测试计划&…

The Sandbox:虚拟游戏世界生态系统详解

元宇宙由区块链、软件基础、移动应用、控制台等组成&#xff0c;是一个虚拟空间&#xff0c;结合了增强现实&#xff08;AR&#xff09;、虚拟现实&#xff08;VR&#xff09;和在线游戏等元素。它强调互操作性&#xff0c;允许用户在不同的虚拟平台之间自由切换。与传统的现实…

病理AI领域的常用开源工具汇总

小罗碎碎念 本期推文主题&#xff1a;病理AI领域的常用开源工具汇总 我们有快一周的时间没见啦&#xff0c;所以&#xff0c;这一期推文带来一些比较有实用价值的资源。 我总结了5个病理AI领域常用的软件&#xff0c;用专用于注释的&#xff0c;也有包含整个处理流程的&#x…

【Linux】UDP 协议

目录 1. UDP 协议2. UDP 协议的特点:3. UDP 协议的格式4. UDP 的缓冲区基于UDP的应用层协议 1. UDP 协议 UDP (User Datagram Protocol) 是一种面向数据报的传输层协议, 是传输层的重要协议之一; UDP协议提供了一种无连接, 不可靠的数据传输服务; 适用于要求源主机以恒定速率…

主控制类,项目小结,实时更新UI

1.用户的信息进行更改&#xff0c;上传请求&#xff0c;服务端进行直接操作数据库&#xff0c;返回请求&#xff0c;客户端根据返回的请求&#xff0c;进行更新界面。 按照我前一篇所说的&#xff0c;写好了主控制类&#xff0c;和第二线程接受服务端的信息&#xff0c;这时候…

【Hot100】LeetCode—416. 分割等和子集

目录 题目1- 思路2- 实现⭐152. 乘积最大子数组——题解思路 3- ACM 实现 题目 原题连接&#xff1a;416. 分割等和子集 1- 思路 理解为背包问题 思路&#xff1a; 能否将均分的子集理解为一个背包&#xff0c;比如对于 [1,5,11,5]&#xff0c;判断能否凑齐背包为 11 的容量…

leetcode算法题之接雨水

这是一道很经典的题目&#xff0c;问题如下&#xff1a; 题目地址 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨水。 解法1&#xff1a;动态规划 动态规划的核心就是将问题拆分成若干个子问题求解&#…

2024算法、高性能计算与人工智能国际学术会议(AHPCAI 2024)

2024算法、高性能计算与人工智能国际学术会议&#xff08;AHPCAI 2024&#xff09; 2024 International Conference on Algorithms, High Performance Computing and Artificial Intelligence 2024年8月14-16日 | 中国-郑州 2024中国算力大会正在发起“算力中国最佳学术论文…

今天我们聊聊C#的并发和并行

并发和并行是现代编程中的两个重要概念&#xff0c;它们可以帮助开发人员创建高效、响应迅速、高性能的应用程序。在C#中&#xff0c;这些概念尤为重要&#xff0c;因为该语言提供了对多线程和异步编程的强大支持。本文将介绍C#中并发和并行编程的关键概念、优点&#xff0c;并…

Langchain核心模块与实战[7]:专业级Prompt工程调教LLM[输入输出接口、提示词模板与例子选择器的协同工程]

Langchain核心模块与实战[7]:专业级Prompt工程调教LLM[输入输出接口、提示词模板与例子选择器的协同工程] 1. 大模型IO接口 任何语言模型应用的核心元素是…模型的输入和输出。LangChain提供了与任何语言模型进行接口交互的基本组件。 提示 prompts : 将模型输入模板化、动态…

【LeetCode:3096. 得到更多分数的最少关卡数目+ 前缀和】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

百度,有道,谷歌翻译API

API翻译 百度&#xff0c;有道&#xff0c;谷歌API翻译&#xff08;只针对中英相互翻译&#xff09;,其他语言翻译需要对应from&#xff0c;to的code 百度翻译 package fills.tools.translate; import java.util.ArrayList; import java.util.HashMap; import java.util.Lis…

宠物空气净化器哪款除臭效果好?质量好的养狗空气净化器排名

作为一个宠物家电小博主&#xff0c;炎炎夏日&#xff0c;家中的宠物给你带来的不仅仅是温暖的陪伴&#xff0c;还有那挥之不去的宠物异味。普通空气净化器虽然能够应对一般的空气净化需求&#xff0c;但对于养猫家庭特有的挑战&#xff0c;如宠物毛发、皮屑和异味等&#xff0…

大模型微调部署实战及类GPT工具的高效使用

大家好,我是herosunly。985院校硕士毕业,现担任算法研究员一职,热衷于大模型算法的研究与应用。曾担任百度千帆大模型比赛、BPAA算法大赛评委,编写微软OpenAI考试认证指导手册。曾获得阿里云天池比赛第一名,CCF比赛第二名,科大讯飞比赛第三名。授权多项发明专利。对机器学…

CSS3雷达扫描效果

CSS3雷达扫描效果https://www.bootstrapmb.com/item/14840 要创建一个CSS3的雷达扫描效果&#xff0c;我们可以使用CSS的动画&#xff08;keyframes&#xff09;和transform属性。以下是一个简单的示例&#xff0c;展示了如何创建一个类似雷达扫描的动画效果&#xff1a; HTM…

libtins初探-抓包嗅探

libtin 一、概述1. 可移植性2. 特性 二、基础知识1. PDU2. 地址类3. 地址范围类4. 网络接口5. 写pcap文件 三、嗅探1.嗅探基础2. 嗅探器配置3. 循环嗅探4. 使用迭代器嗅探6. 包对象7. 读取pcap文件8. 包的解析 四、发送包1. 发送网络层pdu2. 发送链路层pdu3. 发送和接收响应校验…