您的位置:首页 > 其它

[LeetCode Solution 1] 236.Lowest Common Ancestor of a Binary Tree

2015-11-19 22:33 459 查看
【题目概述】:

  给定一棵二叉树T,以及二叉树中的两个节点p和q,试求p和q的最近公共祖先。而所谓p和q最近公共祖先,是指同时拥有p和q两个子孙的所有节点中,深度最大的节点(这里认为一个节点可以是自己的子孙)。

  具体说来要完成如下的一个函数:

  TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q);

  其中root为指向所给树根节点的指针;p,q为指向待考察的两节点的指针;要求返回指向最近公共祖先的指针;

  题目同时给出了如下的数据结构表示树中结点:
Definition for a binary tree node.
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}

  };

【可行解题方法】:

方法一:基于先根遍历的深度搜索算法

  这不是我想出来的方法,而是一种网上流传的方法,先呈上C++代码,稍后再分析:

  

class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if (root == NULL) return NULL;
       //此时已无节点可供向下搜索,也同时表明此条路径上无p或q,返回空指针

if (root == p || root == q) return root;
        //如果root已为p或q,则不用再搜索以root为根节点的子树了,直接返回p或q的指针

TreeNode *L = lowestCommonAncestor(root->left, p, q);
TreeNode *R = lowestCommonAncestor(root->right, p, q);
if (L && R) return root;
return L ? L : R;
}
};


   对于每一层递归,L和R接受了更深一层的递归返回的值,将L和R结合起来看,不外乎有四类可能结果:

  1. L = NULL,R = NULL; 表明以root->left为根节点的子树和以root->right为根节点的子树均不包含节点p和q。那么,当前层递归向上一层递归返回的值也是NULL。表明以root为根节点的子树不含p和q

  2. L = NULL, R指向p; 表明以root->left为根节点的子树不包含节点p和q,但以root->right为根节点的子树仅包含节点p。那么,当前层递归向上一层递归返回的指针指向p。表明以root为根节点的子树仅含节点p

  3. L指向p,R指向q;表明以root->left为根节点的子树仅包含节点p,而以root->right为根节点的子树仅包含节点q。此时可以断定,当前的root就是p和q的最近公共祖先。我们不妨记当前的root为ans(意为题目的答案)。那么,当前层递归向上一层递归返回的指针指向ans。表明以root为根节点的子树含节点p和q

  4.L = NULL,R指向ans,表明以root->left为根节点的子树不包含节点p和q,但以root->right为根节点的子树同时包含p和q。那么,当前层递归向上一层递归返回的指针指向ans。表明以root为根节点的子树含节点p和q(注意此时的root不同于ans,是p和q的公共祖先,但已不是最近公共祖先了)。但没有关系, 指向ans的指针由于返回至上一层递归而得到了保留。这样,我们总可以在最上层函数的出口处得到指向ans的指针。

显然,由于p和q,L和R的等价性,可能的结果还有更多。比如:L = NULL, R指向q;L指向ans, R = NULL等。但不会超出如上所说的四种结果类型。为了更好的显示深搜回溯时的深层函数向浅层函数的传值情况,附示例如下:

  

class Solution {
public:
void markAllNode(TreeNode* root, TreeNode*p, TreeNode* q, TreeNode** queue,
int* level, int& origin_p, int& origin_q, int& level_p, int& level_q)
//进行层次遍历,为所有节点编号
{
int front = 0,//指向队首元素下标
rear = 0,//指向队尾元素下标
current_level = -1;//指示当前待考察节点所在的层数,为了后面的循环处理方便,设为-1
queue[0] = root;//根节点事先入队
level[0] = 0;//处在第0层的第一个节点是编号为0的根节点(也是处在0层的唯一节点)
if (p == root)
{
origin_p = 0;
level_p = 0;
}
if (q == root)
{
origin_q = 0;
level_q = 0;
}
while (front <= rear)
{
if (level[current_level + 1] == front)
//发现待扩展节点为新一层的第一个节点
{
current_level++;//更新当前考察节点所在层数
while (!queue[front]->left && !queue[front]->right)
{
front++;
if (front > rear)
{
return;
}
}
level[current_level + 1] = rear + 1;//预设下一层第一个节点编号
}
if (!queue[front]->left && !queue[front]->right)
{
front++;
continue;
}//欲扩展的节点无孩子
if (queue[front]->left)//左孩子存在
{
rear++;
queue[rear] = queue[front]->left;
if (p == queue[rear])
{
origin_p = rear;
level_p = current_level + 1;
}//判断p是否指向该左孩子
if (q == queue[rear])
{
origin_q = rear;
level_q = current_level + 1;
}//判断q是否指向该左孩子
}
if (queue[front]->right)//右孩子存在
{
rear++;
queue[rear] = queue[front]->right;
if (p == queue[rear])
{
origin_p = rear;
level_p = current_level + 1;
}
if (q == queue[rear])
{
origin_q = rear;
level_q = current_level + 1;
}
}
front++;
//front指向下一个待扩展的节点
}
}
int findLatestAncestor(TreeNode** queue, int* level, int descendant, int level_d)
//寻找descendant所表示的节点的双亲,level_d表明节点所处层数
//返回双亲在层次遍历中的编号
{
for (int i = level[level_d - 1]; i < level[level_d]; i++)
//遍历上一层节点
{
if (queue[i]->left == queue[descendant] || queue[i]->right == queue[descendant])
{
return i;
}
}
}
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q)
{
TreeNode* queue[10000];
//queue[i]存放对树进行层次遍历中,编号为i的节点的地址
int level[1000];
//level[i]存放树的第i层节点中,编号最小的结点,假设根结点所处层数为0
int origin_p, origin_q, level_p, level_q;
//origin_p为p指针所指向的节点在层次遍历中的编号,
//origin_q为q指针所指向的节点在层次遍历中的编号,
//level_p为p指针所指向的节点在树中所处的层次,
//level_q为q指针所指向的节点在树中所处的层次,
markAllNode(root, p, q, queue, level, origin_p, origin_q, level_p, level_q);
while (level_p > level_q)
{
origin_p = findLatestAncestor(queue, level, origin_p, level_p);
level_p--;
}//当origin_p所指向的节点在origin_q所指向的节点下方时,寻找origin_p的与origin_q处于同一层次的祖先
while (level_p < level_q)
{
origin_q = findLatestAncestor(queue, level, origin_q, level_q);
level_q--;
}//当origin_q所指向的节点在origin_p所指向的节点下方时,寻找origin_q的与origin_p处于同一层次的祖先
while (origin_p != origin_q)
{
origin_p = findLatestAncestor(queue, level, origin_p, level_p);
level_p--;
origin_q = findLatestAncestor(queue, level, origin_q, level_q);
level_q--;
}//每次循环均同时寻找origin_p和origin_q各自的双亲,并及时进行比较,判断是否找到了共同祖先

return queue[origin_p];
//返回指向p,q共同祖先的指针
}
};


View Code
  分析时间复杂度:

  层次遍历为节点编号时,共访问n次节点。而在寻找p,q的最近公共祖先时,比较坏的情况下,p,q均为叶子节点,而最近公共祖先为根节点,这样基于p的节点访问次数和基于q的节点访问次数也分别近似为n。总的时间复杂度为O(n)。

  在LeetCode的测试平台上,方法一和二的耗时均为24ms。

  【总结】

  方法一:先根遍历,自顶向下,递归实现;方法二:层次遍历,自底向上,非递归实现。我认为方法一好一些,因为它具有更深邃的设计思想和更简洁,更简短,更清晰的代码。方法二以层次遍历的方式为树形结构进行线性排序,使反向寻找双亲成为可能。扩展一些,沿用方法二“为节点标号”的思想,如果我们以中根遍历的方式为节点标号,倘若以编号为关键字,则一棵普通树可以转化为一棵搜索二叉树,则可用此题的兄弟题LeetCode235."Lowest Common Ancestor of a Binary Search Tree"的方法解决。虽然我用中根遍历的方式失败了,但我仍认为这是一种可行的方法。

  【附】如果你看到了我的这第一篇博客,如果我的阐释能对你有些启发,那么这些文字便有了意义。愿我们共同探索,共同进步!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: