《剑指offer》树中两个结点的最低公共祖先

说实话,和这题很有缘分。首先,这个题目给我印象很深,其次,今天在做蘑菇街实习生笔试时遇到了这个题目,所以感觉应该写博客,把这个题目记录下来。

笔试时遇到的题目是:给定二叉树中的两个结点,寻找最低公共节点。

如果树的节点有指向父节点的指针

这是一种情况,是比较简单的情况。
这种情况就相当于求两条链表的公共节点。求解很简单,先把遍历把两条链表的长度求得。加入list1长a,list2长为b,求则a、b中的较大者先从头遍历走a-b步,然后两条链表同时遍历,第一个相同的节点是最低公父节点。

如果树是普通二叉树

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

我在这次笔试就是采用这种解法。

typedef struct treeNode {
  struct treeNode *left;
  struct treeNode *right;
  int val;
} TreeNode;

/* 查看root结点的子树是否包含distri */
bool hasNode(TreeNode *root, TreeNode *distri){
  if (root == NULL){
    return false;
  }
  if (root->val == distri->val){
    return true;
  }
  return hasNode(root->left, distri) || hasNode(root->right, distri);
}

TreeNode *commomFather(TreeNode *root, TreeNode *node1, TreeNode *node2){
  /* 如果root的左右结点没有都包含两个给定的结点 */
  if ( !((hasNode(root->left, node1)&&hasNode(root->left, node2))
          && (hasNode(root->right, node1) && hasNode(root->right, node2)))){
    return root;
  }else if(NULL != commomFather(root->left, node1, node2)){
    return root->left;
  }else if ((NULL != commomFather(root->left, node1, node2)){
    return root->right;
  }
  return NULL;
}

当然我并没有检验,这是凭记忆回想自己在考场写的代码。

更好的解法

上一种方法很明显,我们对同一结点都会重复遍历很多次,我们寻求一种更快的解法。

《剑指offer》提供了一种思路:

使用辅助内存,用两个链表分别保存从根结点到输入两个结点的路径,然后把问题转换为两个链表的最后公共结点。

这种解法为了得到根结点开始到输入的两个结点的两条路径,需要两次遍历树,每一次遍历需要O(n).得到两条路径长度最差是O(n),通常情况下两条路径长度是O(logn).

/* 获取root到node的path */
bool GetNodePath(TreeNode *root, TreeNode *node, list<TreeNode *> &path){
  /* 迭代终止条件 */
  if (root->val == node->val){
    return true;
  }
  path.push_back(root);
  bool found = false;
  vector<TreeNode *>::iterator it = root->m_vChildren.begin();
  while (!found && i < root->m_vChildren.end()){
    found = GetNodePath(*i, node, 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 *root, TreeNode *node1, TreeNode *node2){
  if (root == NULL || node1 == NULL || node2 == NULL){
    return NULL;
  }
  list<TreeNode *> path1;
  GetNodePath(root, node1, path1);
  list<TreeNode *> path2;
  GetNodePath(root, node2, path2);
  return GetLastCommonNode(path1, path2);
}

好了,完

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值