在二叉树(搜索树)中找到两个节点的最近公共祖先(剑指offer)

简介: 在二叉树(搜索树)中找到两个节点的最近公共祖先(剑指offer)

二叉搜索树的最近公共祖先

题目链接

image.png


//方法一:递归!
public int lowestCommonAncestor (TreeNode root, int p, int q) {
            // write code here
            //利用二叉搜索树的特性,左子树小于根,根小于右子树!
            //从而定位到公共祖先节点!
            if(root==null) return -1;
            //pq在该节点两边,所以 root就是公共节点!
            if(root.val>=p&&root.val<=q||root.val<=p&&root.val>=q) return root.val;
            //pq都在该节点左边,说明在左子树
            if(root.val>=q&&root.val>=p)
                return lowestCommonAncestor(root.left,p,q);
            else{
                return lowestCommonAncestor(root.right,p,q);
            }
}

时间复杂度O(N)最坏情况递归遍历完所有节点!

空间复杂度O(N),递归深度最坏为O(N)

image.png


通过两个数组集合保存两个节点的路径值

找到最后一个相等的节点,即是公共祖先!

//  
public ArrayList<Integer> getPath(TreeNode root,int x){
        ArrayList<Integer> path = new ArrayList<>();
        if(root==null) return path;
        while(root.val!=x){//节点值不同,说明还没遍历到 x!
            path.add(root.val);
            //这里不能用两个if判断,因为这里判断一次就会改变,root的指向!!!
            if(root.val>x){
                //说明在左子树!
                root = root.left;
            }else{
                //说明在右子树!
                root = root.right;
            }
        }
       //将 x 节点保存!
       path.add(x);
        return path;
    }
    public int lowestCommonAncestor (TreeNode root, int p, int q) {
        // write code here
        ArrayList<Integer> path1 = getPath(root,p);
        ArrayList<Integer> path2 = getPath(root,q);
        int ret = 0;
        for(int i = 0;i<path1.size()&&i<path2.size();i++){
            //这里的 path1.get(i)不能直接比较相等,包装类需要通过equals比较!
            if(path1.get(i).equals(path2.get(i))){
                ret = path2.get(i);//更新相同值
            }else{
                break;//无相同值,退出循环!
            }
        }
        return ret;
    }

注意:


Integer包装类比较不能直接通过==比较,要使用equals比较!


时间复杂度: O(N)最坏情况,遍历完所有节点,也就是二叉树单分支,变成了链表,所以需要比较所有节点!

空间复杂度:O(N)最坏情况,所有节点都记录保存!

在二叉树中找到两个节点的最近公共祖先

题目链接


描述:

image.png



这里和上面的搜索二叉树不同,不能直接利用搜索二叉树的特性,直接找到该路径!


我们这里只是普通的二叉树,需要遍历所有的节点,然后将其路径找出!


而我们知道找路径就是找到他这一条路径的祖先,也就是说将每个节点的父节点关系保存即可!


我们借助Map键值对保存,key保存当前节点,value保存根节点!


这里我们可以借助队列遍历到o1和o2节点就即可!


//方法一:BFS 广度优先!
public int lowestCommonAncestor (TreeNode root, int o1, int o2) {
        // write code here
        //借助一种数据结构保存o1和o2的路径!
        //我们知道这里的二叉树只是普通的二叉树,并不是二叉搜索
        //所以无法直接定位到节点路径
        //我们的路径无法是该节点的所有父节点,那么我们可以换种思路!
        //通过map记录o1和o2节点的父节点,以及o1和o2的一系列祖宗,通过map键值对映射保存!
        //然后我们在找出o1的路径即可!
        //然后再在o1路径中找到o2或者o2的最近祖先即是最近公共祖先!
        if(root==null) return -1;
        Map<Integer,Integer> map = new HashMap<>();//key保存当前节点,value保存父节点!
        map.put(root.val,Integer.MAX_VALUE);//根节点无父节点!
        //通过队列,进行层序遍历,当遍历到o1和o2节点就退出!
        Queue<TreeNode> q = new LinkedList<>();
        q.offer(root);
        while(!map.containsKey(o1)||!map.containsKey(o2)){
            TreeNode cur = q.poll();
            if(cur.left!=null){
                 //保存当前节点父子关系!
                map.put(cur.left.val,cur.val);
                q.offer(cur.left);
            }
            if(cur.right!=null){
                map.put(cur.right.val,cur.val);
                q.offer(cur.right);
            }
        }
        //将o1和其祖宗路径保存!
        Set<Integer> ancestors = new HashSet<>();
        while(map.containsKey(o1)){
            ancestors.add(o1);
            //找到其父亲,一直到根节点!
            o1 = map.get(o1);
        }
        while(!ancestors.contains(o2)){
            //在o1祖宗中找到o2或者o2的最近祖先
            o2 = map.get(o2);
        }
        return o2;
    }

时间复杂度:O(N) 最坏所有节点都递归遍历一遍!

空间复杂度:O(N) map 和 queue分别最坏O(N)


我们可以采取递归的方式!


o1 ,o2 分别位于root的左右子树上


image.png

o1==root,o2在其子树上!

image.png



o2==root,o2在其子树

image.png


//方法二:递归
 public int lowestCommonAncestor (TreeNode root, int o1, int o2) {
        // write code here
        if(root == null) return -1;
        //找到最近公共节点!
        return help(root,o1,o2).val;
    }
    public TreeNode help(TreeNode root,int o1,int o2){
        if(root==null||root.val==o1||root.val==o2){
            //递归到空||找到了o1或者o2
            return root;
        }
        //看左子树是否存在o1或者o2节点
        TreeNode left = help(root.left,o1,o2);
        TreeNode right = help(root.right,o1,o2);
        if(left==null){
            //左子树为空,说明o1和o2节点在右子树!
            return right;
        }
        if(right==null){
            return left;
        }
        //说明o1和o2分别在两边!
        return root;
    }

目录
相关文章
|
7月前
|
存储
二叉树详解(深度优先遍历、前序,中序,后序、广度优先遍历、二叉树所有节点的个数、叶节点的个数)
二叉树详解(深度优先遍历、前序,中序,后序、广度优先遍历、二叉树所有节点的个数、叶节点的个数)
代码随想录 Day13 二叉树 LeetCode T104 二叉树的最大深度 T111 二叉树的最小深度 T222完全二叉树的节点个数
代码随想录 Day13 二叉树 LeetCode T104 二叉树的最大深度 T111 二叉树的最小深度 T222完全二叉树的节点个数
60 0
|
算法
代码随想录Day14 LeetCodeT110平衡二叉树 T257二叉树的所有路径 T404 左叶子之和
代码随想录Day14 LeetCodeT110平衡二叉树 T257二叉树的所有路径 T404 左叶子之和
35 0
|
算法
代码随想录算法训练营第十五天 | LeetCode 104. 二叉树的最大深度、559. N 叉树的最大深度、111.二叉树的最小深度、222. 完全二叉树的节点个数
代码随想录算法训练营第十五天 | LeetCode 104. 二叉树的最大深度、559. N 叉树的最大深度、111.二叉树的最小深度、222. 完全二叉树的节点个数
58 0
剑指offer-7.二叉树的下一个节点
剑指offer-7.二叉树的下一个节点
38 1
|
算法 C++
剑指offer(C++)-JZ86:在二叉树中找到两个节点的最近公共祖先(数据结构-树)
剑指offer(C++)-JZ86:在二叉树中找到两个节点的最近公共祖先(数据结构-树)
|
机器学习/深度学习 算法 Java
代码随想录训练营day16|104.二叉树的最大深度 559.n叉树的最大深度 111.二叉树的最小深度 222.完全二叉树的节点个数...
代码随想录训练营day16|104.二叉树的最大深度 559.n叉树的最大深度 111.二叉树的最小深度 222.完全二叉树的节点个数...
剑指offer 07. 二叉树的下一个节点
剑指offer 07. 二叉树的下一个节点
63 0
剑指offer 58. 二叉搜索树的第k个结点
剑指offer 58. 二叉搜索树的第k个结点
55 0
|
算法 开发工具 git
LeetCode算法小抄-- 最近公共祖先 和 完全二叉树的节点个数
LeetCode算法小抄-- 最近公共祖先 和 完全二叉树的节点个数