1、题目:输入两个树结点,求他们的最低公共祖先。
解法:
本题看似是一个题目,其实是一组题目。这就要求我们应该主动地与面试官交流,以获得准确的题目信息。
1)比如,这树是不是二叉树?如果是二叉树,并且是二叉搜索树,是可以找到公共结点的。
假设是二叉搜索树,因为它是排序过的,位于左子树的结点都比父结点小,而位于右子树的结点都比父结点大,我们只需要从树的根结点开始和两个输入的结点进行比较。
如果当前结点的值比两个结点的值都大,那么最低的共同父结点一定是在当前结点的左子树中,于是下一步遍历当前结点的左子结点。如果......都小,那么....右子树中,于是....右子结点。
这样在树中从上到下找到的第一个在两个输入结点的值之间的结点,就是最低的公共祖先。
2)如果这棵树只是普通的树怎么办?那么树的结点中有没有指向父结点的指针?
如果树中的每个结点(除根结点外)都有一个指向父结点的指针,这个问题可以转换为求两个链表的第一个公共结点。
假设树结点中指向父结点的指针是pParent,那么从树的每一个叶结点开始都有一个由指针pParent串起来的链表,这些链表的尾指针都是树的根结点。输入两个结点,那么这两个结点位于两个链表上,他们的最低公共祖先刚好是这两个链表的第一个公共结点。
比如输入的两个结点分别为F和H,那么F在链表F->D->B->A上,而H在链表H->E->B->A上,这两个链表的第一个交点B刚好也是他们的最低公共祖先。
3)假设这颗树是普通的树,并且树中的结点没有指向父结点的指针。
所谓两个结点的公共祖先,指的是这两个结点都出现在某个结点的子树中。我们可以从根结点开始遍历一棵树,每遍历到一个结点时,判断两个输入结点是不是在它的子树中。如果在子树中,则分别遍历它的所有子结点,并判断两个输入结点是不是在他们的子树中。
这样从上到下一直找到的第一个结点,他自己的子树中同时包含两个输入的结点而它的子结点却没有,那么该结点就是最低的公共祖先。
这里可以用辅助内存,用两个链表分别保存从根结点到输入的两个结点的路径,然后把问题转换成两个链表的最后公共结点。
bool GetNodePath(TreeNode* pRoot, TreeNode* pNode, list<TreeNode*>& path)
{
if(pNode == pRoot)
return true;
path.push_back(pRoot);
bool found = false;
Vector<TreeNode*>::iterator i = pRoot->m_vChildren.begin();
while(!found && i<pRoot->m_vChildren.end())
{
found = GetNodePath(*i, pNode, path);
++i;
}
if(!found)
path.pop_back();
return found;
}
TreeNode* GetLastCommonNode
(const list<TreeNode*>& path1, const list<TreeNode*>& path2)
{
list<TreeNode*>::const_iterator iterator1 = path1.begin();
list<TreeNode*>::const_iterator iterator2 = path2.begin();
TreeNode* pLast = NULL;
while(iterator1 != path1.end() && iterator2 != path2.end())
{
if(iterator1 == iterator2)
pLast = iterator1;
iterator1 ++;
iterator2 ++;
}
return pLast;
}
TreeNode* GetLastCommonParent(TreeNode* pRoot, TreeNode* pNode1, TreeNode* pNode2)
{
if(pRoot == NULL || pNode1 == NULL || pNode2 == NULL)
return NULL;
list<TreeNode*> path1;
GetNodePath(pRoot, pNode1, pNode1);
list<TreeNode*> path2;
GetNodePath(pRoot, pNode2, pNode2);
return GetLastCommonNode(path1, path2);
}