面试题50_树中两个节点的最低公共祖先

面试题50_树中两个节点的最低公共祖先

1、关于问题的转化流程:

树——二叉树(排序二叉树)——二叉树(普通二叉树,但是节点中含有指向父节点的指针)——普通的树。


2、如果是这课树是二叉树,并且是排序二叉树,是可以找到给定两个结点的最低公共祖先的。

直观思路:排序二叉树是排序过的,位于左子树的结点值都比父节点小,位于右子树的结点值都比父节点大。

那么,我们只需要从排序二叉树的根节点开始和输入的两个结点进行比较。

如果当前结点的值比两个结点的值都大,那么最低的公共祖先肯定在当前结点的左子树中,于是下一步遍历当前结点的左子树即可。如果当前结点的值比两个结点的值都小,那么最低的公共祖先肯定在当前结点的右子树中,于是下一步遍历当前结点的右子树即可。这样,在树中从上到下找到的第一个在两个给定结点的值之间的结点,就是我们寻求第一个公共最先结点。


3、如果这棵树不是排序二叉树,只是一棵普通的二叉树而已,但是每个结点都存在指向父结点的指针,也是可以找到给定两个结点的最低公共祖先的。

直观思路:如果每个结点都存在指向父节点的指针,那么这个问题可以转化为寻求两个单链表的第一个公共结点。

假设树节点中指向父节点的指针是pParent,那么从树的每一个叶子节点到树的根节点都存在一条由指针pParent 连接起来的单链表。

输入两个结点,那么这两个结点位于两个链表上,它们的最低公共祖先刚好就是这两个单链表的第一个公共结点。

比如输入的两个结点分别是:F 和 H。则F 结点位于单链表:F—D—B—A;H 结点位于单链表:H—E—B—A。

所有它们的第一个公共结点B 就是它们的最低公共祖先。


4、现在假设这棵树是普通的二叉树,没有指向父节点的指针,其实是可以找到给定两个结点的最低公共祖先的。

直观思路:我们首先得到一条从根节点到树中某一节点的路径,这就要求我们在遍历的时候,需要有一个辅助内存来保存路径。比如,我们用前序遍历的方式来得到从根节点到 H 结点的路径过程是这样的:

(1)先遍历根节点A,把A 存放到路径中去,现在路径中有一个节点A ;

(2)遍历到B,把B 存放到路径中去,此时路径为A——B;

(3)遍历到D,把D 存放到路径中去,此时路径为A——B——D;

(4)遍历到F,把F 存放到路径中去,此时路径为A——B——D——F;

(5)F 已经没有子节点了,因此这条路径不可能到达结点H。于是把F 从路径中删除掉,变成A——B——D;

(6)遍历D 的右孩子G,和节点F 一样,这条路径也不能到达H。遍历完G 之后,路径仍然是A——B——D;

(7)由于D 的所有子节点都遍历完了,不可能通过D 达到结点H,所有D 不在从A 到H 的路径中,所有删除D,变成A——B;

(8)遍历E,把E 加入到路径中去,此时路径变成A——B——E;

(9)遍历H,发现已经达到目标结点,A——B——E;就是根节点到H 结点必须经过的路径。

同样的,我们按照这种方法再得到另外一个条指定节点的路径A——B——D;接着,我们求出它们的最后一个公共结点B,也就是F 和H 的最低公共祖先。


时间复杂度:两次遍历树,每一次遍历的时间复杂度都是O(n)。

空间复杂度:得到的路径最差情况是O(n),普通情况两条路径的长度是O(logN)。


最难的是题4的代码,如下:

//题目4
#include<iostream>
#include<list>
using namespace std;
struct BiTree
{
	int value;
	BiTree *pLeft;
	BiTree *pRight;
	BiTree(int x):value(x),pLeft(nullptr),pRight(nullptr) {}
};
//得到指定节点的路径
bool GetNodePath(BiTree *pRoot, BiTree *pNode, list<BiTree *> &path)
{
	if(pRoot==nullptr || pNode==nullptr)
		return false;
	path.push_back(pRoot);
	bool found=false;
	if(pRoot==pNode)
	{
		found=true;
		return found;
	}
	if(!found && pRoot->pLeft)
		found=GetNodePath(pRoot->pLeft,pNode,path);
	if(!found && pRoot->pRight)
		found=GetNodePath(pRoot->pRight,pNode,path);
	if(!found)
		path.pop_back();
	return found;
}
//得到两条路径的最后公共节点
BiTree *GetLastCommomNode(const list<BiTree *> path1, const list<BiTree *> path2)
{
	list<BiTree *>::const_iterator iterator1=path1.cbegin();
	list<BiTree *>::const_iterator iterator2=path2.cbegin();
	BiTree *pLast=nullptr;
	while(iterator1 != path1.cend() && iterator2 != path2.cend())
	{
		if(iterator1 == iterator2)
			pLast=*iterator1;
		++iterator1;
		++iterator2;
	}
}

//综合起来的函数
BiTree *GetLastCommomNodeParent(BiTree *pRoot, BiTree *pNode1, BiTree *pNode2)
{
	if(pRoot==nullptr || pNode1==nullptr || pNode2==nullptr)
		return nullptr;
	list<BiTree *> path1;
	list<BiTree *> path2;
	GetNodePath(pRoot,pNode1,path1);
	GetNodePath(pRoot,pNode2,path2);
	return GetLastCommomNode(path1,path2);
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值