剑指Offer(第二版)面试题68:树中两个节点的最低公共祖先节点

题目:

输入两个树节点,求它们的最低公共祖先节点。

一、二叉搜索树

**应聘者:**这棵树是不是二叉树?

**面试官:**是二叉树,并且是二叉搜索树。

思路:

二叉搜索树是经过排序的,位于左子树的节点都比父节点小,位于右子树的节点都比父节点大。既然要找最低的公共祖先节点,我们可以从根节点开始进行比较。

若当前节点的值比两个节点的值都大,那么最低的祖先节点一定在当前节点的左子树中,则遍历当前节点的左子节点;

反之,若当前节点的值比两个节点的值都小,那么最低的祖先节点一定在当前节点的右子树中,则遍历当前节点的右子节点;

这样,直到找到一个节点,位于两个节点值的中间,则找到了最低的公共祖先节点。

struct TreeNode
{
	int val;
	TreeNode* left;
	TreeNode* right;
	TreeNode(int x) : val(x), left(nullptr), right(nullptr){}
};

TreeNode* GetLastCommonParent(TreeNode* pRoot, TreeNode* pNode1, TreeNode* pNode2)
{
	if (pRoot == nullptr || pNode1 == nullptr || pNode2 == nullptr)
		return nullptr;
	if (pRoot->val > pNode1->val && pRoot->val > pNode2->val)
		return GetLastCommonParent(pRoot->left, pNode1, pNode2);
	else if (pRoot->val < pNode1->val && pRoot->val < pNode2->val)
		return GetLastCommonParent(pRoot->right, pNode1, pNode2);
	else
		return pRoot;

}

二、普通的二叉树,树的节点中有指向父节点的指针

**面试官:**如果只是普通的二叉树呢?

**应聘者:**树的节点中有没有指向父节点的指针?

**面试官:**为什么需要指向父节点的指针?

**应聘者:**如果存在parent指针,则分别从输入的p节点和q节点指向root根节点,其实这就是两个单链表。问题转化为求两个单链表相交的第一个公共节点。

(此题可以用《剑指offer》中面试题52:两个链表的第一个公共节点,求解)

三、普通的二叉树,树的节点中没有指向父节点的指针

**面试官:**那如果不存在parent指针呢?

**应聘者:**所谓两个节点的公共祖先,指的是这两个节点都出现在某个节点的子树中。我们可以从根节点开始遍历一棵树,每遍历到一个节点时,判断两个输入节点是不是在它的子树中。如果在子树中,则分别遍历它的所有子节点,并判断两个节点是不是在它们的子树中。这样从上到下一直找到的第一个节点,它自己的子树中同时包含两个输入的节点而它的子节点却没有,那么该节点就是最低的公共祖先。

/*
此解法是《剑指offer》上的表达的思想,但此解法少考虑了一种情况
     5
    / \
   3
  / 
 2 
当求节点2和节点3的公共祖先时,只能是节点5,而这种情况,在下面这种解法没有考虑这种情况,所以此解法是不完善的   
*/
struct TreeNode
{
	int val;
	TreeNode* left;
	TreeNode* right;
	TreeNode(int x) : val(x), left(nullptr), right(nullptr){}
};

bool FindTreeNode(TreeNode* pRoot, TreeNode* pNode)
{
	if (pRoot == nullptr || pNode == nullptr)
		return false;
	if (pRoot == pNode)
		return true;
	bool found = FindTreeNode(pRoot->left, pNode);
	if (!found)
		return FindTreeNode(pRoot->right, pNode);
	return found;
}

TreeNode* GetLastCommonParent(TreeNode* pRoot, TreeNode* pNode1, TreeNode* pNode2)
{
	if (pRoot == nullptr || pNode1 == nullptr || pNode2 == nullptr || pRoot == pNode1 || pRoot == pNode2)
		return nullptr;
	if (FindTreeNode(pRoot->left, pNode1))
	{
		if (FindTreeNode(pRoot->right, pNode2))
			return pRoot;
		else
			return GetLastCommonParent(pRoot->left, pNode1, pNode2);
	}
	else
	{
		if (FindTreeNode(pRoot->left, pNode2))
			return pRoot;
		else
			return GetLastCommonParent(pRoot->right, pNode1, pNode2);
	}
}

**面试官:**听起来不错。很明显,这种思路会对同一节点重复遍历很多次。你想想看还有没有更快的算法?

**应聘者:**我的想法是用两个链表分别保存从跟节点到输入的两个节点的路径,然后把问题转换成两个链表的最后公共节点。

/*
此解法考虑了,上面说的那种情况
*/
struct TreeNode
{
	int val;
	TreeNode* left;
	TreeNode* right;
	TreeNode(int x) : val(x), left(nullptr), right(nullptr){}
};

bool GetNodePath(TreeNode* pRoot, TreeNode* pNode, vector<TreeNode* > &path)
{
	if (pRoot == nullptr)
		return false;
	path.push_back(pRoot);
	if (pRoot == pNode)
		return true;
	
	bool found = false;
	found = GetNodePath(pRoot->left, pNode, path);

	if (!found)
		found = GetNodePath(pRoot->right, pNode, path);

	if (!found)
		path.pop_back();

	return found;
}

TreeNode* GetLastCommonNode(const vector<TreeNode* > &path1, const vector<TreeNode* > &path2)
{
	TreeNode* last = nullptr;
	TreeNode* pre = nullptr;
	int i = 0;
	while(i < path1.size() && i < path2.size())
	{
		if (path1[i] == path2[i])
		{
			pre = last;
			last = path1[i];
		}
		else
			return last;
		++i;
	}
	/*
	当求得两个节点在从根节点到叶节点一条路径时,返回last前一个节点即可
	*/
	return pre;
}

TreeNode* GetLastCommonParent(TreeNode* pRoot, TreeNode* pNode1, TreeNode* pNode2)
{
	if (pRoot == nullptr || pNode1 == nullptr || pNode2 == nullptr || pRoot == pNode1 || pRoot == pNode2)
		return nullptr;
	vector<TreeNode* > path1;
	vector<TreeNode* > path2;
	GetNodePath(pRoot, pNode1, path1);
	GetNodePath(pRoot, pNode2, path2);

	return GetLastCommonNode(path1, path2);
}

四、普通的树

(不知道树有多少个子树的情况)

struct TreeNode
{
	int val;
	vector<TreeNode* > children;//由于是普通树,不知有多少个子树,所以用vector保存子树
	TreeNode(int x) : val(x){}
};

bool GetNodePath(TreeNode* pRoot, TreeNode* pNode, vector<TreeNode* > &path)
{
	if(pRoot == nullptr)
		return false;
	path.push_back(pRoot);
	if (pRoot == pNode)
		return true;
	bool found = false;
	vector<TreeNode* >::iterator ite = pRoot->children.begin();
	while (!found && ite < pRoot->children.end())
	{
		found = GetNodePath(*ite, pNode, path);
		++ite;
	}
	if (!found)
		path.pop_back();
	return found;
}

TreeNode* GetLastCommonNode(const vector<TreeNode* > &path1, const vector<TreeNode* > &path2)
{
	vector<TreeNode* >::const_iterator ite1 = path1.begin();
	vector<TreeNode* >::const_iterator ite2 = path2.begin();

	TreeNode* pLast = nullptr;
	TreeNode* pre = nullptr;
	while (ite1 != path1.end() && ite2 != path2.end())
	{
		if (*ite1 == *ite2)
		{
			pre = pLast;
             pLast = *ite1;
		}	
		else
			break;
		++ite1;
		++ite2;
	}
	/*
	当求得两个节点在从根节点到叶节点一条路径时,返回pLast前一个节点即可
	*/
	if(ite1 == path1.end() || ite2 == path2.end())
		return pre;
	else
		return pLast;
}

TreeNode* GetLastCommonParent(TreeNode* pRoot, TreeNode* pNode1, TreeNode* pNode2)
{
	if (pRoot == nullptr || pNode1 == nullptr || pNode2 == nullptr || pRoot == pNode1 || pRoot == pNode2)
		return nullptr;
	vector<TreeNode* > path1;
	vector<TreeNode* > path2;
	GetNodePath(pRoot, pNode1, path1);	
	GetNodePath(pRoot, pNode2, path2);

	return GetLastCommonNode(path1, path2);
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值