【公共祖先】二叉树专题

news2024/11/27 23:53:17

里面涉及多个plus题

  • 前言
  • 1.二叉树的最近公共祖先
  • 2.二叉搜索树的最近公共祖先
  • 3.二叉树的最近公共祖先II
  • 4.二叉树的最近公共祖先III
  • 5.二叉树的最近公共祖先IV

前言

公共祖先这一类题目,难度不大,但是非常实用,也是面试问到概率比较大的一类题目。

为什么实用呢?主要在Git领域:

git pull这个命令默认是使用merge方式将远端别人的修改拉倒本地,如果带上参数,git pull -r,就会使用rebase的方式将远端修改拉倒本地。
这二者最直观的区别就是:merge 方式合并的分支会看到很多「分叉」,而 rebase 方式合并的分支就是一条直线。但无论哪种方式,如果存在冲突,Git 都会检测出来并让你手动解决冲突。

那么问题来了,Git 是如何检测两条分支是否存在冲突的呢?

以 rebase 命令为例,比如下图的情况,我站在 dev 分支执行 git rebase master,然后 dev 就会接到 master 分支之上:
在这里插入图片描述
Git的做法是,首先,找到这两条分支的最近公共祖先 LCA,然后从 master 节点开始,重演 LCA 到 dev 几个 commit 的修改,如果这些修改和 LCA 到 master 的 commit 有冲突,就会提示你手动解决冲突,最后的结果就是把 dev 的分支完全接到 master 上面。

至于Git是如何找到两条不同分支的最近公共祖先,这就是本篇要将的经典算法。

1.二叉树的最近公共祖先

公共祖先有两种情况,情况1是确实有第三个节点为公共祖先,情况2是p或者q是公共祖先。

TreeNode* find(TreeNode* root, int val1, int val2){
	if(root == nullptr) return nullptr;
	//前序位置
	if (root->val == val1 || root->val == val2) {
	// 情况2
	// root是不是咱们要找的
		return root;
	}
	// root不是咱要找的,咱就去左边找一找
	TreeNode* left = find(root->left, val1, val2);
	// root不是咱要找的,咱就去右边找一找
	TreeNode* right = find(root->right, val1, val2);
	//后序位置
	//汇总左右的情况,看咱找到没
	//情况1
	if (left != nullptr && right != nullptr) {
		// 当前节点是 LCA 节点
		return root;
	}
	return left != nullptr ? left : right;
}

if (root->val == val1 || root->val == val2) 放在后序位置也可以有一样的效果,但是必然会降低效率,后序位置就需要遍历所有节点了。

2.二叉搜索树的最近公共祖先

对应二叉搜索树而言,没有必要老老实实遍历,因为可以利用他左小右大的性质,将当前节点的值与 val1 和 val2 作对比即可判断当前节点是不是 LCA

假设 val1 < val2,那么 val1 <= root.val <= val2 则说明当前节点就是 LCA;若 root.val 比 val1 还小,则需要去值更大的右子树寻找 LCA;若 root.val 比 val2 还大,则需要去值更小的左子树寻找 LCA。

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        // 保证 val1 较小,val2 较大
        int val1 = min(p->val, q->val);
        int val2 = max(p->val, q->val);
        return find(root, val1, val2);
    }

    // 在 BST 中寻找 val1 和 val2 的最近公共祖先节点
    TreeNode* find(TreeNode* root, int val1, int val2) {
        if (root == nullptr) {
            return nullptr;
        }
        if (root->val > val2) {
            // 当前节点太大,去左子树找
            return find(root->left, val1, val2);
        }
        if (root->val < val1) {
            // 当前节点太小,去右子树找
            return find(root->right, val1, val2);
        }
        // val1 <= root->val <= val2
        // 则当前节点就是最近公共祖先
        return root;
    }
};

3.二叉树的最近公共祖先II

和第一题的区别是,如果p或者q不存在树中,要返回null,那么在第一个的find函数中,有这样一段代码:

// 前序位置
if (root.val == val1 || root.val == val2) {
    // 如果遇到目标值,直接返回
    return root;
}

现在我们就不能p或者q存在就返回公共节点了,而是需要都存在,那么需要咱定义两个bool类型的变量去记录有没有遍历到p或者q,同时,判断的地方也不要放在前序,为了对二叉树进行完全的搜索,应该把他放在后序:

class Solution {
public:
    // 用于记录 p 和 q 是否存在于二叉树中
    bool foundP = false, foundQ = false;

    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        TreeNode* res = find(root, p->val, q->val);
        if (!foundP || !foundQ) {
            return nullptr;
        }
        // p 和 q 都存在二叉树中,才有公共祖先
        return res;
    }

    // 在二叉树中寻找 val1 和 val2 的最近公共祖先节点
    TreeNode* find(TreeNode* root, int val1, int val2) {
        if (root == nullptr) {
            return nullptr;
        }
        TreeNode* left = find(root->left, val1, val2);
        TreeNode* right = find(root->right, val1, val2);

        // 后序位置,判断当前节点是不是 LCA 节点
        if (left != nullptr && right != nullptr) {
            return root;
        }

        // 后序位置,判断当前节点是不是目标值
        if (root->val == val1 || root->val == val2) {
            // 找到了,记录一下
            if (root->val == val1) foundP = true;
            if (root->val == val2) foundQ = true;
            return root;
        }

        return left != nullptr ? left : right;
    }
};

4.二叉树的最近公共祖先III

这题不太一样,和第一题的区别在于,每个节点都包含其父节点的指针,意思是node* p,p->parent可以找到p的父节点,在这样的背景下找p和q的公共祖先

可以将p和q所在的树枝当做链表
在这里插入图片描述
设两个指针分别从两个给定节点出发,如果两个节点不等,则继续往前一步。如果某个节点到达根节点,则跳到另一个节点最初的位置。最终两个指针一定会相遇在交点处,因为到交点处的路径上面指针走过的路程为L1 + L3 + L2,下面的指针走过的路程为L2 + L3 + L1
(更简单的情况是L1 == L2,则直接找到)

class Solution {
public:
    Node* lowestCommonAncestor(Node* p, Node * q) {
        Node *a = p, *b = q;
        while(a != b){
            if(a == nullptr) a = q;
            else a = a->parent;
            if(b == nullptr) b = p;
            else b = b->parent;
        }
        return a;
    }
};

5.二叉树的最近公共祖先IV

输入的不是p和q,而是一个包含若干节点的列表nodes,返回这些节点的最近公共祖先

和第一题是一样的

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, vector<TreeNode*>& nodes) {
        // 将列表转化成哈希集合,便于判断元素是否存在
        unordered_set<int> values;
        for(auto node : nodes) {
            values.insert(node->val);
        }
        
        return find(root, values);
    }

    // 在二叉树中寻找 values 的最近公共祖先节点
    TreeNode* find(TreeNode* root, unordered_set<int>& values) {
        if(root == nullptr) {
            return nullptr;
        }
        // 前序位置
        if(values.find(root->val) != values.end()){
            return root;
        }

        TreeNode* left = find(root->left, values);
        TreeNode* right = find(root->right, values);
        // 后序位置,已经知道左右子树是否存在目标值
        if (left != nullptr && right != nullptr) {
            // 当前节点是 LCA 节点
            return root;
        }

        return left != nullptr ? left : right;
    }
};

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

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

相关文章

夜间数据库IO负载飙升?MySQL批量删除操作引发的问题排查

目录 问题现象 问题分析 修改建议 总结 问题现象 近日&#xff0c;某用户反馈他们的MySQL数据库实例在凌晨时段会频繁出现IO负载急剧上升的情况&#xff0c;这种状态会持续一段时间&#xff0c;随后自行恢复正常。为了查明原因&#xff0c;该用户通过DBdoctor工具收集了相…

DLL中函数导出时的注意事项

1.使用.def文件导出函数 1.1示例代码:使用stdcall 关键字 和 extern "C" 关键字修饰 dll中函数 BOOL APIENTRY DllMain( HMODULE hModule,DWORD ul_reason_for_call,LPVOID lpReserved) {switch (ul_reason_for_call){case DLL_PROCESS_ATTACH:case DLL_THREAD_ATT…

sahi密集检测的推理技巧

最近在做一些计数的项目&#xff0c;样本中存在一些非常密集的目标&#xff0c;如果混杂一起训练指标很难达到要求&#xff0c;所以考虑在训练时不加入密集目标&#xff0c;训练使用正常的样本&#xff0c;在推理时使用密集检测方案。 在高分辨率图像中检测小目标一直是一个技…

【Qt+Python项目构建】- 02 Qt creator 14.0 + PySide6 如何让图像控件的尺寸变化和窗口一致

前言&#xff1a;【这是个AI不会回答的问题】 Qt Creator 新的版本又发出了&#xff0c;Pyside6 有很多新功能。但是&#xff0c;一些传统的方法要被淘汰了。 一个经典的例子是&#xff1a; 我有个一个图像要显示在Form里面的图像控件上&#xff0c;OK&#xff0c; 我现在拖…

Unity实现自定义图集(一)

以下内容是根据Unity 2020.1.0f1版本进行编写的   Unity自带有图集工具,包括旧版的图集(设置PackingTag),以及新版的图集(生成SpriteAtlas)。一般来说,unity自带的图集系统已经够用了,但是实际使用上还是存在一些可优化的地方,例如加载到Canvas上的资源,打图集不能…

JVM(学习预热 - 走进Java)(持续更新迭代)

目录 一、彻底认识Java虚拟机 开创世纪&#xff1a;Sun Classic 开创世纪&#xff1a;Exact VM 武林霸主&#xff1a;HotSpot VM 移动端虚拟机&#xff1a;Mobile/Embedded VM “三大”其二&#xff1a;BEA JRockit/IBM J9 VM 软硬结合&#xff1a;BEA Liquid VM/Azul VM…

更新子节点的优化策略1:目标old节点的位置预测

更新子节点的优化策略1&#xff1a;目标old节点的位置预测&#xff1a; 如果 oldStartVnode 和 newStartVnode 是同一个节点&#xff0c;直接 patchVnode&#xff0c;同时 oldStartIdx、newStartIdx 索引都加 1&#xff08;向右移动&#xff09;如果 oldEndVnode 和 newEndVno…

PE结构之 重定位表

那么,我们找到了某个 需要修改的绝对地址 的RVA, 将这个RVA转换成FOA后,这个绝对地址是读DWORD ,还是QWORD? 就是说,32位和64位是否有区别? 实验: 找到重定位表的数据,并观察在内存中和文件中的区别 将引用dll的exe文件,设置一下基址 同时DLL文件的基址和EXE文件设置一样,并…

肝了4天,我用ChatTTS和LLM让deeplearning.ai课程说上流畅中文

以下是「 豆包MarsCode 体验官」优秀文章&#xff0c;作者X2046。 我们都知道外网上有很多优秀的视频教程平台&#xff0c;比如 Coursera 和 deeplearning.ai。尤其是后者&#xff0c;由吴恩达老师与OpenAI、Langchain、LlamaIndex、AutoGen等公司和作者合作&#xff0c;推出了…

Spring Cloud Netflix Hystrix 熔断器讲解和案例示范

在分布式微服务架构中&#xff0c;每个服务之间相互依赖&#xff0c;当某个服务出现故障或延迟时&#xff0c;如果没有有效的故障隔离机制&#xff0c;可能导致整个系统雪崩式的失败。Netflix Hystrix 作为一种熔断器模式&#xff0c;旨在通过隔离服务之间的调用&#xff0c;提…

通过移动访问控制增强数据中心安全性

在当今数据驱动的世界里&#xff0c;信息是新的黄金标准&#xff0c;数据中心安全已成为每个 IT 部门的首要任务。数据隐私和道德管理不再仅仅是最佳实践&#xff0c;而是法律要求。因此&#xff0c;风险比以往任何时候都要高。 然后是内部威胁问题。根据 IBM 的 《2024 年数据…

Python案例--copy复制

在Python编程中&#xff0c;数据的复制是一个常见且重要的操作&#xff0c;它涉及到赋值、浅拷贝和深拷贝三种不同的概念。正确理解这三种操作对于编写高效且正确的程序至关重要。本文将通过一个简单的Python示例&#xff0c;探讨这三种数据复制方式的区别及其应用场景&#xf…

数据结构 ——— 单链表oj题:环状链表(求出环的入口节点)

目录 题目要求 手搓一个简易带环链表 代码实现 题目要求 给定一个链表的头节点 head&#xff0c;返回链表开始入环的第一个节点&#xff0c;如果链表无环&#xff0c;则返回NULL 手搓一个简易带环链表 代码演示&#xff1a; struct ListNode* n1 (struct ListNode*)mal…

深度学习:循环神经网络—RNN的原理

传统神经网络存在的问题&#xff1f; 无法训练出具有顺序的数据。模型搭建时没有考虑数据上下之间的关系。 RNN神经网络 RNN&#xff08;Recurrent Neural Network&#xff0c;循环神经网络&#xff09;是一种专门用于处理序列数据的神经网络。在处理序列输入时具有记忆性…

Mac上强大的菜单栏管理工具

想要Mac用的好&#xff0c;各种工具少不了&#xff0c;一款好用的软件对于提高使用效率和使用舒适度来说非常必要&#xff0c;iBar-强大的菜单栏图标管理工具 随着 Mac 运行的软件增加&#xff0c;状态栏中的图标也越来越多&#xff0c;不仅看得眼花缭乱&#xff0c;而且刘海屏…

基于SpringBoot+Vue的农场管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…

ORM框架简介

什么是ORM&#xff1f; ORM&#xff08;Object-Relational Mapping&#xff0c;对象关系映射&#xff09;是一种编程技术&#xff0c;用于在关系数据库和对象程序语言之间转换数据。ORM框架允许开发者以面向对象的方式来操作数据库&#xff0c;而不需要编写复杂的SQL语句。简单…

CMake 属性之目录属性

【写在前面】 CMake 的目录属性是指在特定目录&#xff08;及其子目录&#xff09;范围内有效的设置。 这些属性不同于全局变量或目标&#xff08;Target&#xff09;属性&#xff0c;它们提供了一种机制&#xff0c;允许开发者为项目中的不同部分定义不同的构建行为。 通过目录…

HarmonyOS NEXT应用开发实战(二、封装比UniApp和小程序更简单好用的网络库)

网络访问接口&#xff0c;使用频次最高。之前习惯了uniapp下的网络接口风格&#xff0c;使用起来贼简单方便。转战到鸿蒙上后&#xff0c;原始网络接口写着真累啊&#xff01;目标让鸿蒙上网络接口使用&#xff0c;简单程度比肩uniapp&#xff0c;比Axios更轻量级。源码量也不多…

Spring Cloud全解析:链路追踪之springCloudSleuth简介

文章目录 springCloudSleuth简介链路追踪&#xff1f;SpringCloudSleuth术语链路示意图zipkin依赖配置 springCloudSleuth简介 链路追踪&#xff1f; 什么是链路追踪&#xff1f;就是将一次分布式请求还原成调用链路&#xff0c;将一次分布式请求的调用情况集中展示&#xff…