情况一.所查找的树是二叉搜索树
这是最简单的情况,我们知道在二叉搜索树中左子树的所有结点的值一定比根结点小,右子树中的所有结点的值一定比根结点大。根据二叉树的这个性质,我们只需要从根结点开始和两个输入结点的值进行比较,如果当前结点的值比输入的两个结点的值都大,那仫最低的公共祖先一定在左子树中,则继续遍历左子树;如果当前结点的值比输入的两个结点的值都小,那么最低的公共祖先一定在右子树中,则继续遍历右子树。这样在树中从上到下找到的第一个在两个输入结点的值之间的结点,就是最低的公共祖先。
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};
TreeNode* PublicLowParent(TreeNode *root, int x1, int x2)
{
if(root == NULL)
return NULL;
TreeNode *pcur = root;
while(pcur)
{
if(pcur->val > x1 && pcur->val > x2)
{
pcur = pcur->left;
}
else if(pcur->val < x1 && pcur->val < x2)
{
pcur = pcur->right;
}
else
return pcur;
}
return false;
}
情况二.普通的树但存在父指针
对于二叉搜索树我们可以很容易的找出最低的公共祖先,那仫如果不是二叉搜索树呢?甚至都不是二叉树又该如何找找到它的最低祖先结点呢?我们给这个不是搜索二叉树的树加上一个指向父结点的指针会发生什仫情况呢?
看到这个变态的要求这使我想起了之前实现过的一道面试题:找到两个链表的第一个公共结点。我觉得这两道题是有相同点的,为什仫呢?之前不是说查找的是最后的一个公共结点为什仫可以转化成在链表中的第一个结点呢?
试想一下,如果这棵树存在指向父结点的指针,从两个不同的结点向根结点看过去,我们发现它们具有相同的尾结点,输入的两个结点我们可以假设它在两条链表上,他们的最低公共祖先刚好是这两条链表的第一个公共结点。在这里用到了逆向思维的方式。
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
struct TreeNode *parent;
TreeNode(int x) :
val(x), left(NULL), right(NULL),parent(NULL) {
}
};
int Len(TreeNode *phead)
{
int size = 0;
TreeNode *pcur = phead;
while(pcur)
{
size++;
pcur = pcur->parent;
}
return size;
}
//转换为求两个链表的公共节点
TreeNode* PublicLowParent(TreeNode *root, TreeNode *node1, TreeNode *node2)
{
if(root == NULL || node1 == NULL || node2 == NULL)
return NULL;
//求两个链表的长度。
int size1 = Len(node1);
int size2 = Len(node2);
TreeNode *p1 = node1;
TreeNode *p2 = node2;
//差值,
int step = size1-size2;
if(step > 0)
{
while(step--)
{
p1 = p1->parent;
}
}
else
{
step = -step;
while(step--)
{
p2 = p2->parent;
}
}
while(p1 != p2)
{
p1 = p1->parent;
p2 = p2->parent;
}
return p1;
}
情况三.普通的树不存在父结点
1).递归的方法
此时就没有什仫技巧可用了,只能使用最原始的办法啦,从根结点开始遍历,每遍历到一个结点的时候就判断两个输入的结点是否在它的子树中,如果在,则分别遍历它的所有子节点,并判断输入的两个结点是否在他们的子节点中,这样一直向下遍历,直到找到最低祖先为止。
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};
//这里我们假设就是普通的二叉树。只有左子树和右子树。
TreeNode* PublicLowParent(TreeNode *root, TreeNode *node1, TreeNode *node2)
{
if(root == NULL || node1 == NULL || node2 == NULL)
return NULL;
TreeNode *first = NULL;
TreeNode *second = NULL;
if(root == node1 || root == node2) //根 如果一个节点是另一个节点的祖先节点,两个节点的最近公共节点就是这个节点了。
{
return root;
}
first = PublicLowParent(root->left, node1, node2); //在左子树找。
second = PublicLowParent(root->right, node1, node2);//在右子树找。
// // 如果有其他子树的话,还需要在其他子树中找。
if(first && second ) //返回两个非空指针,说明这两个节点在两个子树上,则此时的根节点就是公共节点。
return root;
return first?first:second; //如果有一个节点为空,则说明两个节点都在一颗子树上。
}
但是上面的办法是很耗费时间的,有些结点被遍历了多次,这样它的效率就有点低了,其实可以使用栈这种辅助内存的方式来解决的。其实利用辅助内存的方法也就是保存路径,而栈的后进先出的特性又使得该问题转化成找第一个公共结点的问题了
5和4的路径先后入栈,如上图所示我发现还是需要找到两条路径的路径差的,先让路径长的先pop掉路径差个元素,然后两个栈的元素一起出栈,知道找到第一个key值相同的结点就是最低的公共祖先了。
bool FindPath(TreeNode *root, TreeNode *node, stack<TreeNode*> &s)
{
if(root == NULL)
{
return false;
}
s.push(root);
if(root == node)
return true;
bool left = FindPath(root->left, node, s);
bool right = FindPath(root->right, node, s);
if(left == false && right == false) //左右子树都没有找到这个节点,说明此时这个节点不在路径当中,弹出。
{
s.pop();
return false;
}
return true;
}
TreeNode* PublicLowParent2(TreeNode *root, TreeNode *node1, TreeNode *node2)
{
stack<TreeNode*>s1;//保存node1的路径
stack<TreeNode*>s2;//保存node2的路径
FindPath(root, node1, s1);
FindPath(root, node2, s2);
int step = s1.size()-s2.size();
if(step > 0)
{
while(step--)
{
s1.pop();
}
}
else
{
step = -step;
while(step--)
{
s2.pop();
}
}
while(!s1.empty() && !s2.empty() && s1.top() != s2.top())
{
cout << s1.top() <<endl;
cout << s2.top() <<endl;
s1.pop();
s2.pop();
}
return s1.top();
}