一.题目要求
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
二.题目难度
中等
三.输入样例
示例 1:
输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出:3
解释:节点 5 和节点 1 的最近公共祖先是节点 3。
示例 2:
输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出:5
解释:节点 5 和节点 4 的最近公共祖先是节点 5 。因为根据定义最近公共祖先节点可以为节点本身。
示例 3:
输入:root = [1,2], p = 1, q = 2
输出:1
提示:
- 树中节点数目在范围 [2, 105] 内。
- -109 <= Node.val <= 109
- 所有 Node.val 互不相同 。
- p != q
- p 和 q 均存在于给定的二叉树中。
四.解题思路
想了一种自己比较好理解的方法。
对于当前结点来说,我们要进行以下步骤的判别:
- 定义递归函数的返回值为一个pair<bool, bool>,含义是当前结点是否是所给第一个,第二个结点的祖先。
- 对于每一个结点root来说,pair<bool, bool> isLeftParent()获取他左孩子的情况,即他的左孩子是否是所给<第一个数,第二个数>的祖先,pair<bool, bool> isRightParent()获取它右孩子的情况。
例如上述二叉树,假设我们要求6和4的公共最近祖先,采用后序遍历 - 对于6来说,我们进行判断,若①他的左孩子或者右孩子中,任意一个为第一个数6的祖先②该结点本身的值和第一个数6相等,只要满足任意一条,就视为该结点是第一个数6的祖先。
- 因为结点6没有孩子,所以他的左孩子和右孩子都不可能是所给的6和4的祖先,他的左右孩子返回给他{false, false},{false, false} ,而后判断6本身是否和所给两个数的其中一个相等,这里6 = 所给第一个数,所以这一栈帧中返回{true, false},表示这个结点为根的树中有第一个数的祖先,没有第二个数的祖先。
- 其他情况同理,当递归到某一个结点,其既是第一个数又是第二个数的祖先时,我们便找到了公共祖先(这里是5)。
五.代码实现
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
bool result = false;
bool left = false;
bool right = false;
isParent(root, p->val, q->val, result);
return ans;
}
pair<bool, bool> isParent(TreeNode* root, int left, int right, bool &result)
{
//边界
if(!root) return {false, false};
if(result) return {true, true};
//左子树是否是两个数的祖先
pair<bool, bool> isLeftParent = isParent(root->left, left, right, result);
//右子树是否是两个数的祖先
pair<bool, bool> isRightParent = isParent(root->right, left, right, result);
//只要这个结点的左子树或右子树有一个是第一个数的祖先,便认为该结点也是这个数的祖先
bool l = isLeftParent.first || isRightParent.first;
//只要这个结点的左子树或右子树有一个是第二个数的祖先,便认为该结点也是这个数的祖先
bool r = isLeftParent.second || isRightParent.second;
//再判断该结点本身的值是否和这两个数相等
if(l || root->val == left) l = true;
if(r || root->val == right) r = true;
//如果都满足且尚未找到
if(l && r && !result) {
result = true;
ans = root;
}
return {l, r};
}
private:
TreeNode* ans = nullptr;
};
六.题目总结
对于递归题,先找解题思想(分类讨论),再找边界,再定顺序和微操, 然后验证。