Lowest Common Ancestor of a Binary Search Tree 二叉搜索树的最近公共祖先 系列(简单,中等)

简单版

给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为: “对于有根树T的两个结点u、v,最近公共祖先表示一个结点x,满足x是u、v的祖先且x的深度尽可能大。”(一个节点也可以是它自己的祖先

例如,给定二叉搜索树:  root = [6,2,8,0,4,7,9,null,null,3,5]

        _______6______
       /              \
    ___2__          ___8__
   /      \        /      \
   0      _4       7       9
         /  \
         3   5

示例 1:

输入: root, p = 2, q = 8
输出: 6 
解释: 节点 2 和节点 8 的最近公共祖先是 6。

示例 2:

输入: root, p = 2, q = 4
输出: 2
解释: 节点 2 和节点 4 的最近公共祖先是 2, 因为根据定义最近公共祖先节点可以为指定节点自身。

思路:

这道题关键是二叉搜索树(它或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树。),所以我们不需要复杂的考虑(下文会说)。

如果待查找的节点p和q都比当前节点root小,那就在“左子树”中继续搜索

如果待查找的节点p和q都比当前节点root大,那就在“右子树”中继续搜索

如果待查找的节点p和q一个比root大,一个比root小,就返回root节点,当前root节点就是答案。

代码如下:

    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
	   while (root) {
		   if (root->val > p->val && root->val > q->val) {
			   root = root->left;
		   }
		   else if (root->val < p->val && root->val < q->val) {
			   root = root->right;
		   }
		   else {
			   return root;
		   }
	   }
	   return root;        
    }

中等版

给定一棵二叉树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义: “对于有根树T的两个结点u、v,最近公共祖先表示一个结点x,满足x是u、v的祖先且x的深度尽可能大。”(一个节点也可以是它自己的祖先

例如,给定如下二叉搜索树:  root = [6,2,8,0,4,7,9,null,null,3,5]

        _______3______
       /              \
    ___5__          ___1__
   /      \        /      \
   6      _2       0       8
         /  \
         7   4

示例 1:

输入: root, p = 5, q = 1
输出: 3
解释: 节点 5 和节点 1 的最近公共祖先是节点 3。

示例 2:

输入: root, p = 5, q = 4
输出: 5
解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为指定节点自身。

思路:

方法一:用前序遍历遍历整个树,找到p和q所在的节点,用两个变量veotor<int> tmp1,tmp2分别记录p和q的路径,如下图树结构:

        _______3______
       /              \
    ___5__          ___1__
   /      \        /      \
   6      _2       0       8
         /  \
         7   4

输入p=6,q=2,则tmp1的元素为:

[3,5,6]

tmp2的元素为:

[3,5,2]

则遍历tmp和tmp2相同坐标的元素,从i=0开始,一直到找到最后一个相同的元素node,即使答案。

代码如下:

   void findNodeInTree(TreeNode* root, TreeNode* node, vector<TreeNode*> &vec, bool& flag) {
	   if (!root || flag) {
		   return;
	   }
	   vec.push_back(root);
	   if (root == node) {
		   flag = true;
		   return;
	   }
	   findNodeInTree(root->left, node, vec, flag);
	   if (flag) {
		   return;
	   }
	   findNodeInTree(root->right, node, vec, flag);
	   if (flag) {
		   return;
	   }

	   vec.pop_back();
   }
   TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
	   if (!root) {
		   return root;
	   }
	   bool flag_p = false;
	   bool flag_q = false;
	   vector<TreeNode*> p_vec;
	   vector<TreeNode*> q_vec;
	   findNodeInTree(root, p, p_vec, flag_p);
	   findNodeInTree(root, q, q_vec, flag_q);
	   int i = 0;
	   while (!p_vec.empty() && !q_vec.empty() && i<p_vec.size() && i<q_vec.size() && p_vec[i] == q_vec[i]) {
		   i++;
	   }
	   if (i<=p_vec.size() && i<=q_vec.size() && p_vec[i-1] == q_vec[i-1]) {
		   return p_vec[i - 1];
	   }
	   return nullptr;
   }
这里需要注意: 我们用flag来控制如果找到目标节点就直接返回,不要在递归寻找了

方法二

这是discuss部分的答案,简直简单到爆炸!!!!中心思想是最后找到的最低公共子节点也是原树的一部分,则我们首先找出p和q的节点在哪,相同的原理,如果p和q都是分布在左右子树上,则返回根节点,否则返回左右子树不为空的节点。

        _______3______
       /              \
    ___5__          ___1__
   /      \        /      \
   6      _2       0       8
         /  \
         7   4

还是以这个图为例,p=6,q=7。

1:根节点3开始:3!=6且3!=7所以调用3->left和3->right。

2:3->left的值为5,不满足条件,调用5->left,5->right。   3->right的值为1,不满足条件,调用1->left,1->right(这里对3的情况不做分析,直接给结论,如果该节点的所有子树不含目标节点则直接返回nullptr)

3:5->left等于6,满足条件,返回6。5->right等于2,不满足条件,继续调用2->left,2->right

4:2->left返回7,2->right返回nullptr。

5:对于2节点而言,left节点返回7,right节点返回nullptr,由于不满左右分布,所以返回不为空的那个节点(节点7)。

6:对于节点5的右节点返回7,这时5节点的左右节点返回值都不为空,所以返回根节点5本身。

7:对于3节点,左节点返回5,右节点返回nullptr,所以返回不为空的那个节点(节点5)。最后5节点就是答案。

总结:哈哈哈~~~带大家在大脑里过了一遍递归,似不似都凌乱了!!!别着急,我们来总结规律,看看能从这种方法中学习到什么。首先,先用递归找到目标节点p和q。

        _______3______
       /              \
    ___5__          ___1__
   /      \        /      \
   6      _2       0       8
         /  \
         7   4

然后把目标节点往顶(根节点移动),规则是逐次返回不为空的那一项,如果两项都不为空,就返回root节点。移动路线图如下:


一直移动到p和q节点是目标节点的左右节点,这时返回目标节点5:

(蓝箭头表示至少有一个子节点为空,红箭头表示两个子节点都不为空)

类似两个人在相遇之前一直过着自己的人生轨迹,相遇以后变“合体”一起往前走,哈哈哈哈!!!

代码如下:

   TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
	   //返回目标节点
	   if (!root || root == p || root == q) {
		   return root;
	   }
	   //查看左子树是否有目标节点,有就返回目标节点(单身)或目标节点的父节点(合体),没有就返回nullptr
	   TreeNode* left = lowestCommonAncestor(root->left, p, q);
	   //查看右子树是否有目标节点,有就返回目标节点(单身)或目标节点的父节点(合体),没有就返回nullptr
	   TreeNode* right = lowestCommonAncestor(root->right, p, q);
	   //左右子树都不为空,则“合体”
	   if (left && right) {
		   return root;
	   }
	   //返回不为空的那一项
	   return left ? left : right;
   }
但是这种方法前提是p和q必须在树中存在,否则结果就不对了。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值