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

本文详细介绍了如何在二叉搜索树和普通二叉树中找到两个节点的最近公共祖先。针对二叉搜索树,提出了递归和路径值比较两种方法;对于普通二叉树,通过广度优先搜索和递归策略来解决。文章讨论了时间复杂度和空间复杂度,并提供了关键代码实现。
摘要由CSDN通过智能技术生成

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

题目链接

image-20220623104756281 \

//方法一:递归!
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)

alt

  • 通过两个数组集合保存两个节点的路径值
  • 找到最后一个相等的节点,即是公共祖先!
//  
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-20220624130527609

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

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

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

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

  • 这里我们可以借助队列遍历到o1o2节点就即可!

//方法一: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) mapqueue分别最坏O(N)


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

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

image-20220624132242936

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

    image-20220624132359650

    • o2==root,o2在其子树

    image-20220624132601267

//方法二:递归
 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;
    }

时间复杂度:O(N) 最坏所有节点都递归遍历一遍!
空间复杂度:O(N) 单分支树,退化成了链表!

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

bug 郭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值