题目来源:https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/
个人公众号:【小猿君的算法笔记】
题目描述
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大 (一个节点也可以是它自己的祖先)。”
示例:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出: 5
解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。
思路分析
题目给出一棵树的两个节点,要求获得这两个节点的最近公共祖先。首先我们要明确一个节点的祖先是什么。
上图中被红色背景标记的是节点 5 的祖先,而被蓝色背景标记的节点是节点 4 的祖先。而节点 3 和节点 5 这两个节点就是 节点 5 和 4 的公共祖先。其中 5 是最近公共祖先。
「思路一」
通过上面的分析,我们可以采取的解题策略是
1、先求出两节点 p 和 q 的节点路径。
2、取两路径最后一个相同的节点。
这样我们就能获得最近公共祖先。
「思路二」
对于 p 节点和 q 节点的出现情况,可以分为以下两大类:
1、p 和 q 分别居于一棵树的左右两子树中,那么它们的最近公共祖先就是这棵树的根节点。
2、p 和 q 在一棵树的同一个子树里面,这时候,我们又可以把整体问题转化为对局部问题的求解,把子树再作为一棵完整的树,进行求解。而归根到底,p、q 如果不在一棵树的两子树中,那么,要么 p 可以作为 q 的祖先节点,要么 q 可以作为 p 的祖先节点。而这个祖先节点,正是最近公共祖先节点。
代码描述
下面使用 Java 对思路二进行代码描述:
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root == null || root.val == p.val || root.val == q.val) {
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;
}
}
通过前面的分析,我们可以知道,我们需要递归,进行递归,我们要先确定下递归的出口,我们用 root 代表根节点,那么当 root == null 时,我们就递归到了尽头。还有一种情况,如果我们找到了节点 p 或者 q,那么我们也可以结束递归了。因为假设找到了 p,那么 q 要么在 p 节点的下面,要么在其它分支上,这一点在递归主体上将有所体现。
在递归主体中,我们当前节点需要获取其左右子树中是否存在 p 和 q。
1、如果左右两边都存在,说明当前节点就是最近公共祖先。
2、如果左右两边一边为空,另一边不为空,那么最近公共祖先要么在其不为空的那一边,要么在其它分支上。
3、如果两边全都为空,说明路线找错了,这条路上根本不存在最近公共祖先。
我们对示例进行稍微的改动,现在要求节点 6 和节点 4 的最近公共祖先,取其中的一小段步骤,进行进一步说明:
节点 7 的左右子节点都为空,因此它向其父节点 2 汇报返回结果 left == null,节点 4 匹配到了,那么它向其父节点 2 汇报返回结果 right == 4。作为局部子树父节点 2 经过一顿比较,向其父节点汇报,自己所在子树中存在节点 4。
作为局部子树父节点 5 将会收到左子树的汇报 left == 6 ,右子树的汇报 right == 4,节点 5 就能确定,自己就是最近公共祖先,因此它再次向其父节点 3 汇报。
根节点 3 将会收到子树的汇报 left == 5,right == null,将会返回 5,而根节点所返回的 5 ,就是我们所需要的最近公共祖先。