236. 二叉树的最近公共祖先

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

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

示例 1:

image1

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

示例 2:

image2

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

示例 3:

image3

输入: root = [1,2], p = 1, q = 2
输出: 1

提示:

  • 树中节点数目在范围 [2, 10^5] 内。
  • -10^9 <= Node.val <= 10^9
  • 所有 Node.val 互不相同 。
  • p != q
  • pq 均存在于给定的二叉树中。

Related Topics

  • 深度优先搜索
  • 二叉树

题目链接: link

解答

本题的难度是 Medium.

写了一个很抽象的代码, 感觉最近写的代码都很抽象, 这里我考虑记录每一个节点当前的深度和父节点的值, 然后再遍历一遍, 找到两个节点的深度, 然后让深度深的节点向上走, 直到两个节点的深度相同, 然后再一起向上走, 直到两个节点相遇, 这个节点就是最近公共祖先. 代码如下:

class Solution {
    private HashMap<Integer, Integer> depthMap = new HashMap<>();
    private HashMap<Integer, Integer> parentMap = new HashMap<>();
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        // 一个HashMap 记录深度, 一个HashMap 记录父节点是什么
        BTS(root,0, null);
        int pVal = p.val;
        int qVal = q.val;
        int pDepth = depthMap.get(pVal);
        int qDepth = depthMap.get(qVal);
        if (pDepth < qDepth){
            for (int i = 0; i < qDepth-pDepth; i++) {
                qVal = parentMap.get(qVal);
            }
        } else {
            for (int i = 0; i < pDepth-qDepth; i++) {
                pVal = parentMap.get(pVal);
            }
        }
        while (pVal != qVal){
            pVal = parentMap.get(pVal);
            qVal = parentMap.get(qVal);
        }
        // 如果这边不许创建新节点, 我甚至打算搞一个 Map 存储节点的引用
        return new TreeNode(pVal);
    }
    private void BTS(TreeNode node, int depth, TreeNode parent){
        if(node == null){
            return;
        }
        if (parent == null){
            parentMap.put(node.val, null);
        } else {
            parentMap.put(node.val, parent.val);
        }
        depthMap.put(node.val, depth);
        if (node.left != null) {
            BTS(node.left, depth+1, node);
        }
        if (node.right != null) {
            BTS(node.right, depth+1, node);
        }

    }
}

因为这个代码实在太抽象了, 因此时间开销 9ms, 只击败了 15.14% 的提交. 现在写一个正经一点的版本.

如果 p 和 q 分别在当前节点的左右子树, 那么当前节点就是最近公共祖先, 如果 p 和 q 都在左子树, 那么就在左子树找, 如果 p 和 q 都在右子树, 那么就在右子树找. 代码如下:

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;
    }
}

这个代码时间开销为 7ms, 击败了 69% 的提交, 这边其实也很抽象, 因为这个代码不是我写的, 我准备写的, 把思路写下来 copilot直接帮我生成了….

但是也懒得再改.