当前位置: 首页 > article >正文

力扣236——二叉树的最近公共祖先

祝大家新年快乐呀,虽这段时间正值过年,但是我们不要忘记停下学习的脚步。今天我们一起看一到力扣上的经典二叉树OJ题,求二叉树两个结点最近的公共祖先。

https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/description/

链接给大家放在这里啦,大家一点即达

首先我们看到题目,这道题并没有告诉我们这是一棵怎么样的二叉树,不是完全二叉,也不是搜索二叉,所以这道题做起来就比较棘手,完全二叉树的话我们可以根据结点的编号查找,搜索二叉我们也可以按照大小确定范围,但是眼前这颗数完全是一个乱序的,所以这道题怎么下手呢?

方法一        

我们不妨先确定要查找的两个结点在根结点的哪边,我们拿5和8举例子

很明显。这两个结点一个在根结点的左边,一个在右边,所以他们最近公共结点肯定就是根节点。

那如果在同一边呢?比如6和4,

结点6和4都在根节点的左子树上,我们可以用刚才的方法,递归根节点的左子树,以结点5为跟,就可以求出他们最近的公共结点为5;思路有了,现在我们编写代码

我们先封装一个函数判断结点是否在这个以root为根结点的二叉树内:

bool IsTree(TreeNode*root,TreeNode* x)
    {
        if(root==nullptr)
        {
            return false;
        }
        return root==x||IsTree(root->left,x)||IsTree(root->right,x);
    }

然后我们在给定的函数内实现出这个函数。

这里教大家一个简便的方法。我们定义几个bool变量:

bool Leftp,Rightp,Leftq,Rightq;//依次代表的含义是p在左子树,P在右子树,q在左子树,q在右子树

然后我们去找p在root的那一边,把返回值给Leftp,让Rightp=!Left;

Leftp=IsTree(root->left,p);
Rightp=!Leftp;

这样写大家能理解什么意思吗?在root的左子树去找结点p的结果赋给Leftp,不管是不是在左边,右边的结果肯定就是!Leftp,假如Leftp是true,那Rightp就是false;如果Leftp是false,那Rightp就是true;二者肯定一真一假,判断q结点在哪边也是如此

Leftq=IsTree(root->left,q);
Rightq=!Leftq;

然后我们判断如果两个结点pq有一个是根节点root的话,那就返回root就可以了

if(p==root||q==root)
        {
            return root;
        }

其次,判断,如果pq一个在根节点左边,一个在右边,那还是返回根节点。即

if((Leftp&&Rightq)||(Rightp&&Leftq))//中间用||连接是因为我们也不知道他们在哪边,反正肯定有一组是真的
{
    return root;
}

然后再判断他们在同一边的情况,代码运行到这里,肯定能证明他们不在同一边,我们先判断同时在左子树的情况:

        else if(Leftp&&Leftq)
        {
            return lowestCommonAncestor(root->left,p,q);
        }

剩下的就是同时在右子树的情况:

else{
        return lowestCommonAncestor(root->right,p,q);
    }

然后我们提交,就可以看到通过啦。但是我们思考一下,这样的写法时间复杂度是很大的。如果这棵树是一个类似于单边的二叉树,也就是另外一边的结点个数很少很少,几乎全部集中在一边,且要找的两个结点都在最后,即:

我们要查找的两个结点在这个位置,它的时间复杂度是多少?可以看到,我们以3为根结点查询1和3在哪边需要走N-2次+N-3次,然后递归以10为根节点查找,再递归以8为根节点查找.......时间复杂度其实是O(N^2)的。

bool IsTree(TreeNode*root,TreeNode* x)
    {
        if(root==nullptr)
        {
            return false;
        }
        return root==x||IsTree(root->left,x)||IsTree(root->right,x);
    }
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        bool Leftp,Rightp,Leftq,Rightq;
        Leftp=IsTree(root->left,p);
        Rightp=!Leftp;
        Leftq=IsTree(root->left,q);
        Rightq=!Leftq;
        if(p==root||q==root)
        {
            return root;
        }
        if((Leftp&&Rightq)||(Rightp&&Leftq))
        {
            return root;
        }
        else if(Leftp&&Leftq)
        {
            return lowestCommonAncestor(root->left,p,q);
        }
        else{
            return lowestCommonAncestor(root->right,p,q);
        }
}

方法二

下面我们介绍第二种方法

第二种方式是我们记录两个结点的路径,即从根结点到找到他们之间所有的结点。例如:

结点6的路径是3->5->6,结点4的路径是3->5->2->4,把他们都放在两个栈里面,比较栈的特点是后进先出,然后比较这个两个栈的第一个相同的栈顶元素。这样的话我们最坏无非就是遍历2次这棵树就可以,定义两个栈,算是以空间换时间。下面我们开始实现

我们首先需要在原函数定义两个栈,一个用于存放p结点的路径,一个存放q结点的路径

TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
stack<TreeNode*> ppath;
stack<TreeNode*>qpath;
}

然后我们需要封装一个函数用来求结点走过的路径Getpath();参数需要根节点,待求结点,栈,即

bool Getpath(TreeNode* root, TreeNode* x, stack<TreeNode*>& path)//传引用是为了我们在调用这个函数时对这个栈的修改可以随时生效,不需要再拷贝

用bool类型是为了我们找到这个结点时就直接返回不再往下走。

下面我们实现这个函数,首先判断根节点为空,那就返回false,接下来先把根节点入栈,然后判断根结点是要求的路径的结点,是的话返回true;

bool Getpath(TreeNode* root, TreeNode* x, stack<TreeNode*>& path)
{
	if (root == nullptr)
	{
		return false;
	}
	path.push(root);
	if (root == x)
	{
		return true;
	}
}

代码走到这里,那就证明根结点不是x,我们递归左子树,如果左子树为真,返回true;左子树遇到空结点返回false,下来递归右子树,右子树也是如此;

if (Getpath(root->left, x, path))
		return true;
	
	if (Getpath(root->right, x, path))
		return true;

代码走到这里,证明根节点及它的左右孩子结点都不是x,我们需要把这个根节点出栈的,比如:

比如我们求结点4的路径,代码运行到查找5的左孩子时,6先进栈,然后递归6的左右子树,都为空,已经把6的左右子树都查找完毕,所以4的路径不经过结点6,那这个时候需要让6出栈,然后返回false,返回上一层递归。

if (root != x)
	{
		path.pop();
	}
	return false;

这个函数就封装完成。

下面写原函数,原函数我们首先要定义两个栈,上面已经定义过,这个是我们先走一遍p的路径,再走一遍q的路径,这个时候ppath和qpath两个栈里面存放着p和q的路径,我们需要让这两个栈的大小相等,因为从根节点到最近公共祖先肯定是完全一样的,这样才能找出公共路径。

while (ppath.size() != qpath.size())
{
	if (ppath.size() > qpath.size())
		ppath.pop();
	else
		qpath.pop();
}

这个时候两栈存放的数据个数相等,只需比较栈顶元素是否相等,不相等再同时出栈,相等时返回栈顶元素就可以啦。

while (ppath.top() != qpath.top())
{
	ppath.pop();
	qpath.pop();
}
return ppath.top();

这样代码就能提交过啦。时间复杂度也很乐观。

bool Getpath(TreeNode* root, TreeNode* x, stack<TreeNode*>& path)
{
	if (root == nullptr)
	{
		return false;
	}
	path.push(root);
	if (root == x)
	{
		return true;
	}
	if (Getpath(root->left, x, path))
		return true;;
	
	if (Getpath(root->right, x, path))
		return true;
	if (root != x)
	{
		path.pop();
	}
	return false;
}
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
    stack<TreeNode*> ppath;
stack<TreeNode*>qpath;
Getpath(root, p, ppath);
Getpath(root, q, qpath);
while (ppath.size() != qpath.size())
{
	if (ppath.size() > qpath.size())
		ppath.pop();
	else
		qpath.pop();
}
while (ppath.top() != qpath.top())
{
	ppath.pop();
	qpath.pop();
}
return ppath.top();
    }


http://www.kler.cn/news/234334.html

相关文章:

  • [2024]常用的pip指令
  • Docker 容器网络:C++ 客户端 — 服务器应用程序。
  • 【北邮鲁鹏老师计算机视觉课程笔记】01 introduction
  • 【服务器部署】Docker环境的安装
  • Linux内核有什么之内存管理子系统有什么——基础篇之struct vm_area_struct(2)
  • Bert与ChatGPT
  • Java多态原理
  • 学习数据结构和算法的第7天
  • 【MySQL】-12 MySQL索引(上篇MySQL索引类型前置-1)
  • 像素、分辨率、公差的概念
  • 相机图像质量研究(11)常见问题总结:光学结构对成像的影响--像差
  • Vue项目创建
  • 【Java】学习笔记:关于java.sql;
  • 基于vue+node.js的校园跳蚤市场系统多商家
  • Python图形用户界面
  • 假期day6
  • OSDI 2023: Conveyor One-Tool-Fits-All Continuous Software Deployment at Meta
  • ###C语言程序设计-----C语言学习(11)#数据的存储和基本数据类型
  • mfc110.dll是什么?解决mfc110.dll丢失windows系统常见问题
  • blender几何节点中样条线参数中的系数(factor)是个什么概念?
  • 2.10日学习打卡----初学RocketMQ(一)
  • Open CASCADE学习|2个TCL命令转C++
  • 【Linux】make和Makefile
  • Tomcat之虚拟主机
  • 基于微信小程序的校园二手交易平台
  • ChatGPT高效提问—prompt常见用法(续篇九)
  • Nginx实战:2-日志配置
  • wireshark抓包问题及学习
  • Uniapp(uni-app)学习与快速上手教程
  • vue3初识