【二叉树】【力扣】二叉树的最近公共祖先

二叉树的最近公共祖先

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

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
在这里插入图片描述

第一种情况:树结点中保存了双亲的位置和值域

如果二叉树结点中保存了双亲的位置和值域的话(比如双亲表示法或者是孩子双亲表示法),可以转换为两个链表求交点的问题
在这里插入图片描述

第二种情况:如果树是二叉搜索树(二叉排序树)

二叉搜索树特点

  1. 根节点比左子树中所有节点都大,比所有右子树中的节点都小;
  2. 根据中序遍历可以得到一个有序序列

如果树是二叉搜索树(二叉排序树),此时分为如下几种情况(假设我们要找的是p,q的公共祖先):

  1. 如果p,q有一个在根的位置 || p,q分别在根节点的左右子树中(q<root && p>root)
    最近的公共祖先一定是根节点
  2. p<root && q<root 即p,q都在root左子树
    递归到左子树中查找
  3. p>root && q>root 即p,q都在root右子树
    递归到右子树中查找

第三种情况:二叉树既不是孩子双亲表示法也不是二叉搜索树,就是一颗孩子表示法的普通二叉树

这种一般情况的求解方式参考情况1和情况2

方法一:(由情况一的思路扩展)
由于是孩子表示法,所以只能从根开始找到p所经结点,到q所经结点,but 我们查看最近公共祖先的时候是从结点往根开始找的,所以此处需要用到栈的特性,将途中所经结点保存再栈中

获取到结点所经的路径:
比如说获取根结点到E点所经路径
在这里插入图片描述

 // 先获取到p,q结点的路径
    public boolean nodePath(TreeNode root, TreeNode node, Stack<TreeNode> path){
        if(null == root){
            // 树为空返回null
            return false;
        }
        // 将当前结点入栈
        path.push(root);
        if(node == root){
            // 如果当前遍历到的结点root就是最终结点node
            // root因为不断递归所以root不是最开始的根节点了,而是遍历到的当前结点
            return true;
        }
        if(nodePath(root.left,node,path) || nodePath(root.right,node,path)){
            // 在root的左子树中找node没找到就在右子树中找,找到了返回true
            return true;
        }
        // 没在树中找到node,所以之前压栈的结点要删掉
        path.pop();
        return false;
    }

得到p,q所经节点后,将他们存储在两个栈中,比较两个栈当中的元素(比较的时候类似于求链表相交,先让元素多的栈弹出元素,一直弹出到两个栈元素个数相等,然后开始比较两个栈的栈顶)

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(null == root ||null == p ||null == q){
            return null;
        }
        Stack<TreeNode> pPath = new Stack<>();
        nodePath(root,p,pPath);

        Stack<TreeNode> qPath = new Stack<>();
        nodePath(root,q,qPath);

        int pSize = pPath.size();
        int qSize = qPath.size();

        while (pSize > 0 && qSize > 0){
            if(pSize > qSize){
                pPath.pop();
                pSize--;
            }else if(pSize < qSize){
                qPath.pop();
                qSize--;
            }else{
                if(pPath.peek() == qPath.peek()){
                    return pPath.peek();
                }else{
                    pPath.pop();
                    qPath.pop();
                    pSize--;
                    qSize--;
                }
            }
        }
        return null;
    }
    // 先获取到p,q结点的路径
    public boolean nodePath(TreeNode root, TreeNode node, Stack<TreeNode> path){
        if(null == root){
            // 树为空返回null
            return false;
        }
        // 将当前结点入栈
        path.push(root);
        if(node == root){
            // 如果当前遍历到的结点root就是最终结点node
            // root因为不断递归所以root不是最开始的根节点了,而是遍历到的当前结点
            return true;
        }
        if(nodePath(root.left,node,path) || nodePath(root.right,node,path)){
            // 在root的左子树中找node没找到就在右子树中找,找到了返回true
            return true;
        }
        // 没在树中找到node,所以之前压栈的结点要删掉
        path.pop();
        return false;
    }
}

方法二:(从第二种情况二叉搜索树当中得到启发)
p,q分别在root的左右子树中–》最近的公共祖先就是root
p,q在root的左子树中–》递归到根节点的左子树中找
p,q在root的右子树中–》递归到根节点的右子树中找

那么如何知道p在根的哪个子树中–》可以转成检测节点是否在一棵树中的题

看到底是在左右子树只需要改变传参,传参是左子树的节点就是查找是否是在左子树中,传参是右子树的节点就是查找是否是在右子树中

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(null == root || null == p || null == q){
            return root;
        }
        if(root == p || root == q){return root;}
        boolean pInLeft = false;
        boolean pInRight = false;
        boolean qInLeft = false;
        boolean qInRight = false;

        // 检测p在左子树还是右子树
        if(isInTree(root.right,p)){
            // p在右子树中
            pInRight = true;
        } else{
            pInLeft = true;
        }

        // 检测q在左子树还是右子树
        if(isInTree(root.right,q)){
            // p在右子树中
            qInRight = true;
        } else{
            qInLeft = true;
        }

        if((pInLeft && qInRight) || (pInRight && qInLeft)) {
            // p,q分别在root的左右子树中--》最近的公共祖先就是root
            return root;
        } else if(pInLeft && qInLeft) {
            // p,q在root的左子树中--》递归到根节点的左子树中找
            return lowestCommonAncestor(root.left,p,q);
        } else {
            // p,q在root的右子树中--》递归到根节点的右子树中找
            return lowestCommonAncestor(root.right,p,q);
        }
    }
        private boolean isInTree(TreeNode root,TreeNode node){
        if(root == null){
            return false;
        }
        if(root == node){
            return true;
        }
        if(isInTree(root.left,node) || isInTree(root.right,node)){
            return true;
        }
        return false;
    }
}

猜你喜欢

转载自blog.csdn.net/qq_43360037/article/details/107892137