简单版:
给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为: “对于有根树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必须在树中存在,否则结果就不对了。