文章目录
面试题04.08
1.问题描述
2.解决方案
解法一:不包含父亲节点的双递归低效解法
思路很简单,但是需要双递归判断,对于每一个结点都要判断p,q是否被cover!
//不包含父亲节点的双递归低效解法
class Solution1 {
public:
//判断p节点是否在以root为根节点的树中
bool covers(TreeNode* root,TreeNode* p){
if(root== nullptr) return false;
if(root!= nullptr&&p== nullptr) return false;
//先检查是不是根
if(root==p) return true;
//再检查在不在子树
return covers(root->left,p)||covers(root->right,p);
}
TreeNode* find(TreeNode* root,TreeNode* p,TreeNode* q){
if(root== nullptr) return nullptr;
//
if(root==p) return p;
if(root==q) return q;
//在find函数内p q肯定是都在root里的
bool a=covers(root->left,p);
bool b=covers(root->left,q);
//如果p,q不在root的一边就证明找到了结果就是root
if(a!=b) return root;
//p q都在左子树就去左子树找共同祖先的
if(a&&b) return find(root->left,p,q);
//p q都在右子树就去右子树找共同祖先的
if(!a&&!b) return find(root->right,p,q);
return nullptr;
}
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
//首先必须检查合理性
if(covers(root,p)== false||covers(root,q)== false) return nullptr;
//在find函数内p q肯定是都在root里的
return find(root,p,q);
}
};
解法二:不包含父亲节点的单递归最优化解法(万能解法)
//万能解法
class Solution4 {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
//1.必要的检查
if(root== nullptr) return nullptr;
//2.可以理解为对于要寻找的节点的判断
if(root==p||root==q) return root;
//3.递归调用等待结果
TreeNode* left=lowestCommonAncestor(root->left,p,q);
TreeNode* right=lowestCommonAncestor(root->right,p,q);
//4.根据上面的left,right 空和不空组成的四种结果进行相应的处理
if(left!= nullptr&&right!= nullptr) return root;
if(left== nullptr&&right== nullptr) return nullptr;
if(left== nullptr&&right!= nullptr) return right;
if(left!= nullptr&&right== nullptr) return left;
//5.为了通过编译实则不可能从这走
return nullptr;
}
};
解法三:深度优先遍历搜索 递归遍历整棵二叉树(力扣官方题解)
1.这个算法的精髓在于,是递归但是不是像解法一那样分左右子树那样找,因为分左右子树那样子是自根向下找,那么每一次判断都需要遍历整棵树,时间复杂度肯定高。
2.此算法实则是深度优先搜索先到叶子节点,叶子结点的flson和frson自然得到了,然后深度优先由叶子向上回溯,那么叶子结点的父节点的flson和frson通过子节点的返回值也得到了,说白了就是从叶子节点开始向上传递flson和frson。
3.那么每个节点的flson和frson都知道了,那就可以在自己的递归层中带入我们算法的核心公式
(lson && rson) || ((root->val == p->val || root->val == q->val) && (lson || rson)),通过这个公式直接判断是否是共同祖先,并且相应的给出返回值回溯到上层计算上层的flson和frson,这个公式上面已经提到。
综上所述,这个算法就是从叶子节点开始遍历整棵树,对整棵树的每一个节点都带入我们的核心公式,然后根据公式的结果判断即可,说白了就是对于每一个节点代入公式是共同祖先就返回,不是就拉倒,去遍历下一个节点,把所有节点都过一遍自然结果就出来了,就这么简单!当然这个公式精确的定义了共同祖先所以才使得算法比较清晰简洁!
//深度优先搜索遍历
class Solution3 {
public:
TreeNode* ans;
bool dfs(TreeNode* root, TreeNode* p, TreeNode* q) {
if (root == nullptr) return false;
bool lson = dfs(root->left, p, q);
bool rson = dfs(root->right, p, q);
if ((lson && rson) || ((root->val == p->val || root->val == q->val) && (lson || rson))) {
ans = root;
}
return lson || rson || (root->val == p->val || root->val == q->val);
}
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
dfs(root, p, q);
return ans;
}
};