树的遍历 之 二叉树中节点的最近公共祖先

298 阅读2分钟

本文正在参与掘金团队号上线活动,点击 查看大厂春招职位

一、题目描述:

这个题目说的是,给你一棵二叉树以及树中两个节点,你要找到这两个节点的最近公共祖先,然后将它返回。

比如说,给你的二叉树是:

      1
    /   \
   2     3
  / \     \
 4   5     6

节点 4 和 5 的最近公共祖先是节点 2;
节点 5 和 6 的最近公共祖先是节点 1;
节点 1 和 6 的最近公共祖先也是节点 1。

二、思路分析:

思路:

  1. 求出路径后,进行逐一比对
  2. 递归法:思路也是找两个目标节点

方法二,递归方法,详细步骤如下:

  1. 如果当前节点为空,或者等于目标节点 pq,则返回当前节点。
  2. 否则递归到左右子树上进行处理,返回值分别为 leftright
  3. 如果 leftright 非空,则说明在左右子树上各找到一个节点,于是当前的根节点就是最近公共祖先
    • 如果 leftright 只有一个非空,则返回那个非空的节点。
    • 如果都为空,就返回空指针。

总而言之:先找对应的节点,再逐级到父节点比对。

举个栗子:

查找节点 4 和 3 的最近公共祖先
      1
    /   \
   2     3
  / \     \
 4   5     6
 
节点 4 和 3 的最近公共祖先是节点 1;


实操步骤:
1. 递归到节点 2,对比左右节点,返回节点 4
   1. 查到目标左节点 4, 返回节点 4
   2. 没有查到目标节点,返回 NULL
   
2. 递归到节点 3, 找到目标节点,返回节点 3

3. 在根节点 1 汇总,发现左右节点都存在,则返回根节点

如图:

tree5.png


三、AC 代码:

public class LeetCode_236 {

    // 方法一:求出路径后,进行逐一比对
    // Time: O(n), Time: O(n), Faster: 23.04%
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        Queue<TreeNode> queueP = new LinkedList<>();
        Queue<TreeNode> queueQ = new LinkedList<>();

        findTreeNode(root, p, queueP);
        findTreeNode(root, q, queueQ);

        while (queueP.size() > queueQ.size()) queueP.poll();
        while (queueP.size() < queueQ.size()) queueQ.poll();

        while (!queueP.isEmpty() && !queueQ.isEmpty()) {
            TreeNode pNode = queueP.poll();
            TreeNode qNode = queueQ.poll();
            if (pNode == qNode) return pNode;
        }
        return null;
    }

    private TreeNode findTreeNode(TreeNode root, TreeNode p, Queue<TreeNode> queue) {
        if (root == null) return null;
        if (root == p) {
            queue.add(p);
            return p;
        }
        TreeNode targetNode = findTreeNode(root.left, p, queue);
        if (targetNode == p) {
            queue.add(root);
            return targetNode;
        }
        targetNode = findTreeNode(root.right, p, queue);
        if (targetNode == p) {
            queue.add(root);
            return targetNode;
        }
        return null;
    }

    // 方法一:求出路径后,进行逐一比对
    // Time: O(n), Space: O(n), Faster: 29.20%
    public TreeNode lcaWithPath(TreeNode root, TreeNode p, TreeNode q) {
        List<TreeNode> ppath = new ArrayList<>();
        List<TreeNode> qpath = new ArrayList<>();
        search(root, p, ppath);
        search(root, q, qpath);
        int i = 0, len = Math.min(ppath.size(), qpath.size());
        while (i < len && ppath.get(i) == qpath.get(i)) ++i;
        return ppath.get(i-1);
    }

    private boolean search(TreeNode root, TreeNode node, List<TreeNode> path) {
        if (root == null) return false;
        path.add(root);
        if (root == node) return true;
        boolean ret = search(root.left, node, path) || search(root.right, node, path);
        if (ret) return true;
        path.remove(path.size()-1);
        return false;
    }

    // 方法二:递归方法
    // Time: O(n), Space: O(n), Faster: 99.91%
    public TreeNode lcaExtend(TreeNode root, TreeNode p, TreeNode q) {
        if (root == null || root == p || root == q) return root;
        TreeNode left = lcaExtend(root.left, p, q);
        TreeNode right = lcaExtend(root.right, p, q);
        if (left != null && right != null) return root;
        else if (left != null) return left;
        else return right;
    }
}

四、总结:

掌握:

  1. 树的性质
  2. 递归思想