Bootstrap

【剑指Offer】个人学习笔记_68_二叉搜索树的最近公共祖先(完结撒花)

刷题日期:上午7:49 2021年5月30日星期日

个人刷题记录,代码收集,来源皆为leetcode

经过多方讨论和请教,现在打算往Java方向发力

主要答题语言为Java

题目:

剑指 Offer 68 - I. 二叉搜索树的最近公共祖先

难度简单131

给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。

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

例如,给定如下二叉搜索树: root = [6,2,8,0,4,7,9,null,null,3,5]

img

示例 1:

输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
输出: 6 
解释: 节点 2 和节点 8 的最近公共祖先是 6

示例 2:

输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4
输出: 2
解释: 节点 2 和节点 4 的最近公共祖先是 2, 因为根据定义最近公共祖先节点可以为节点本身。

说明:

  • 所有节点的值都是唯一的。
  • p、q 为不同节点且均存在于给定的二叉搜索树中。
题目分析

二叉搜索树的特点就是已经是有序的了。

二叉树题目,很有可能还涉及到递归。在搜索中,分情况,如果左边小于根,右边大于根,那么直接返回根节点,如果左边右边都大于,那么去右边找,如果左边右边都小于,那么去左边找。

题目限定了输入肯定存在,那么先找到哪个节点,哪个节点就是最近的公共节点。

初始解答:

没白练啊没白练,完美收官,一次跑通

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    TreeNode res;
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(p.val > q.val) {
            TreeNode tmp = p;
            p = q;
            q = tmp;
        } //确保p小q大
        if(root.val <= q.val && root.val >= p.val) return root;
        if(root.val < p.val) res = lowestCommonAncestor(root.right, p, q);
        if(root.val > p.val) res = lowestCommonAncestor(root.left, p, q);
        return res;
    }
}

执行结果:通过

显示详情 添加备注

执行用时:6 ms, 在所有 Java 提交中击败了100.00%的用户

内存消耗:38.6 MB, 在所有 Java 提交中击败了96.88%的用户

学习他人:

方法一:

宝石L2 (编辑过)2020-02-14

Java题解 简单明了

利用好二叉搜索树的性质。

    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        
        if (root == null)
            return null;
        
        if (root.val > p.val && root.val > q.val)
            return lowestCommonAncestor(root.left, p, q);
        if (root.val < p.val && root.val < q.val)
            return lowestCommonAncestor(root.right, p, q);

        return root;
    }

方法二:

JoCai 2021-02-21

Java:

执行用时:6 ms, 在所有 Java 提交中击败了100.00%的用户

内存消耗:39.3 MB, 在所有 Java 提交中击败了17.36%的用户

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root==null) return null;
        if(p==root || q==root) return root;
        //两个结点都在当前结点的左边
        if(p.val<root.val && q.val<root.val){
            return lowestCommonAncestor(root.left,p,q);//找左边
        }
        //两个结点都在当前结点的右边
        else if(p.val>root.val && q.val>root.val){
            return lowestCommonAncestor(root.right,p,q);//找右边
        }
        //或者一左一右
        else{
            return root;
        }
    }
}

方法三:

mata川L5

2020-03-13

可以写几个case然后画个图看看,很容易 发现规律

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        //当p,q都比root小时,根据二叉搜索树的性质,则p和q的公共祖先一定在root的左子树上
        if (p.val < root.val && q.val < root.val) {
            return lowestCommonAncestor(root.left, p, q);
        }
        //同理,当p,q都比root大,则去root的右子树上找。
        if (p.val > root.val && q.val > root.val) {
            return lowestCommonAncestor(root.right, p, q);
        }
        
        //如果p或q等于root了,则说明这个等于的节点就是公共祖先
        //否则则p和q是在root的两边,则root是公共祖先
        if (p.val == root.val) {
            return p;
        } else if (q.val == root.val) {
            return q;
        } else {
            return root;
        }
    } 
}

方法四:

Roderland 2020-08-06

迭代版

class Solution {
   public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        // if (p==root||q==root) return root;
        // if (p==q) return p;
        while (true) {
            if (root.val>p.val&&root.val>q.val) root=root.left;
            else if (root.val<p.val&&root.val<q.val) root=root.right;
            else return root;
        }
    }
}

方法五

K神

作者:jyd
链接:https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-zui-jin-gong-gong-zu-xian-lcof/solution/mian-shi-ti-68-i-er-cha-sou-suo-shu-de-zui-jin-g-7/
来源:力扣(LeetCode)

方法一:迭代
class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        while(root != null) {
            if(root.val < p.val && root.val < q.val) // p,q 都在 root 的右子树中
                root = root.right; // 遍历至右子节点
            else if(root.val > p.val && root.val > q.val) // p,q 都在 root 的左子树中
                root = root.left; // 遍历至左子节点
            else break;
        }
        return root;
    }
}

优化:若可保证 p.val < q.val ,则在循环中可减少判断条件。

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(p.val > q.val) { // 保证 p.val < q.val
            TreeNode tmp = p;
            p = q;
            q = tmp;
        }
        while(root != null) {
            if(root.val < p.val) // p,q 都在 root 的右子树中
                root = root.right; // 遍历至右子节点
            else if(root.val > q.val) // p,q 都在 root 的左子树中
                root = root.left; // 遍历至左子节点
            else break;
        }
        return root;
    }
}
方法二:递归
class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root.val < p.val && root.val < q.val)
            return lowestCommonAncestor(root.right, p, q);
        if(root.val > p.val && root.val > q.val)
            return lowestCommonAncestor(root.left, p, q);
        return root;
    }
}

题目:

剑指 Offer 68 - II. 二叉树的最近公共祖先

难度简单258

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

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

例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4]

img

示例 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。因为根据定义最近公共祖先节点可以为节点本身。

说明:

  • 所有节点的值都是唯一的。
  • p、q 为不同节点且均存在于给定的二叉树中。
题目分析

不是搜索二叉树,那么就必须找到两个节点才算结束。

咕咕咕L1 (编辑过)2021-05-04

这题的递归理解实际没有那么简单,如果直接把函数理解成求以root为根的树中,pq的最近祖先节点其实是不准确的。 应该对该递归函数的定义进行扩充,这个函数实际上功能是:

  1. 如果在root为根的树中,同时具有pq两个节点,则返回最近祖先
  2. 如果在root为根中的树中,只有其中一个节点,则返回含有的那个节点
  3. 如果两个节点都不存在,则返回nullptr

经过对函数的定义扩充,才符合函数体中返回值的逻辑,而且也恰好能实现需要的功能。

初始解答:

参考上面的分析和解答

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root == null || root == p || root == q) return root;
        TreeNode left = lowestCommonAncestor(root.left, p, q);
        TreeNode right = lowestCommonAncestor(root.right, p, q);
        if(left == null) return right;
        if(right == null) return left;
        return root;
    }
}

执行结果:通过

显示详情 添加备注

执行用时:7 ms, 在所有 Java 提交中击败了99.98%的用户

内存消耗:39.9 MB, 在所有 Java 提交中击败了45.15%的用户

学习他人:

方法一:

…L1 (编辑过)2020-02-13

class Solution {
    /**
     * 二叉树的最近公共祖先
     * 思路:
     * 三种情况:
     * 1、p q 一个在左子树 一个在右子树 那么当前节点即是最近公共祖先
     * 2、p q 都在左子树 
     * 3、p q 都在右子树
     * @param root
     * @param p
     * @param q
     * @return
     */
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if (root == null) {
            return null;
        }
        if (root == p || root == q) {
            return root;
        }
        TreeNode left = lowestCommonAncestor(root.left, p, q);
        TreeNode right = lowestCommonAncestor(root.right, p, q);
        if (left != null && right != null) {
            // p q 一个在左,一个在右
            return root;
        }
        if (left != null) {
            // p q 都在左子树
            return left;
        }
        if (right != null) {
            // p q 都在右子树
            return right;
        }
        return null;
    }
}

方法二:


Nanjun1998

(编辑过)2021-03-17

剑指offer书上的做法,时间效率不高,但也容易理解

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        Deque<TreeNode> stackP = new ArrayDeque<>();
        Deque<TreeNode> stackQ = new ArrayDeque<>();
        getTrace(root,p,stackP); // 获取p和q的路径
        getTrace(root,q,stackQ);
        TreeNode ret = root;
        while(!stackP.isEmpty()&&!stackQ.isEmpty()){
            TreeNode nodeQ = stackQ.poll();
            TreeNode nodeP = stackP.poll();
            if(nodeP.val!=nodeQ.val) return ret;
            ret = nodeP;   //ret为最后一个相同的节点
        }
        return ret;
    }

    private boolean getTrace(TreeNode root , TreeNode node, Deque<TreeNode> stack){       
        stack.addLast(root);
        boolean hasGet=false;
        if(root.val == node.val)  return true;
        if(root.left!=null)  hasGet = getTrace(root.left,node,stack);
        if(!hasGet && root.right!=null&&!stack.isEmpty()) hasGet = getTrace(root.right,node,stack);
        if(!hasGet&&!stack.isEmpty())  stack.removeLast();
        return hasGet;
    }
}

后续遍历,时间效率高,代码简洁优美,但感觉不是很好想_(¦3」∠)_

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root==null||root==p||root==q) return root;
        TreeNode left = lowestCommonAncestor(root.left,p,q);
        TreeNode right = lowestCommonAncestor(root.right,p,q);
        if(left!=null && right!=null) return root;
        if(left!=null) return left;
        if(right!=null) return right;
        return null;
    }
}

方法三:

Brise 3 天前

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root==null){return null;}
        if(root==p||root==q){return root;}
        TreeNode left = lowestCommonAncestor(root.left,p,q);
        TreeNode right = lowestCommonAncestor(root.right,p,q);
        if(left ==null){
            return right; 
        }else if(right==null){
            return left;
        }else{
            return root;
        }
    }
}

方法四:

坏森生L1 2020-10-28

不用判空,p,q就是树内的节点;

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root == null)
            return null;
        
        if(p == root || q == root)
            return root;
        
        TreeNode left = lowestCommonAncestor(root.left,p,q);
        TreeNode right = lowestCommonAncestor(root.right,p,q);

        if(left != null && right != null)
            return root;
        return left == null ? right : left;
    }
}

方法五

K神

作者:jyd
链接:https://leetcode-cn.com/problems/er-cha-shu-de-zui-jin-gong-gong-zu-xian-lcof/solution/mian-shi-ti-68-ii-er-cha-shu-de-zui-jin-gong-gon-7/
来源:力扣(LeetCode)

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root == null || root == p || root == q) return root;
        TreeNode left = lowestCommonAncestor(root.left, p, q);
        TreeNode right = lowestCommonAncestor(root.right, p, q);
        if(left == null) return right;
        if(right == null) return left;
        return root;
    }
}

展开写法

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root == null || root == p || root == q) return root;
        TreeNode left = lowestCommonAncestor(root.left, p, q);
        TreeNode right = lowestCommonAncestor(root.right, p, q);
        if(left == null && right == null) return null; // 1.
        if(left == null) return right; // 3.
        if(right == null) return left; // 4.
        return root; // 2. if(left != null and right != null)
    }
}

总结

以上就是本题的内容和学习过程了,基本上有递归就有循环,断断续续两个月也过来了,后面就是复习,然后接着刷题保持手感了,大家加油啊。

完结撒花~

欢迎讨论,共同进步。

;