236. Lowest Common Ancestor of a Binary Tree
题干简述:
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
Wiki 中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)”。
解题思路
之前,有做过一道类似的题目:235. Lowest Common Ancestor of a Binary Search Tree。本题和 No.235 的最大,也是本质上的区别,在于本题所操作的二叉树是一个普通的二叉树,而 No.235 中操作的是 BST 二叉检索树。得益于二叉检索树的有序性,No.235 很好解决。但是,一个普通的二叉树,我们如何找到指定两个节点的最近公共祖先呢?
首先,让我们仔细看下 LCA 最近公共祖先的定义:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)”。假设我们已经找到了这个 LCA 结点 X,那么,X必然满足以下几种情况之一:
-
X 为 p 或 q 其中某一个,另一个结点在 X 的子树中:
-
X 不为 p 或 q 其中某一个,p,q 分别在 X 的左右子树中:
这样,我们就可以确定 X 满足的条件。提到条件,是不是脑海里立刻回想起递归了?没错,这个条件,就是我们递归的决定返回值的条件。让我们梳理一下递归的四步:
- 输入参数:传入 root 结点;
- 函数的功能/目的:获取当前树中(假定)存在的 p,q 的 LCA 结点;
- 终止条件:输入结点为叶子结点时,又或输入结点等于 p 或 q 时;
- 返回值:这里的返回值需要我们好好思考一下,返回值肯定是当前树中,p,q 的 LCA 结点,问题是我们如何拿到它。结合上面梳理出的 LCA 结点 X 的条件,假定树根节点为 r,递归 r 的左子树得到结果为 left,递归 r 的右子树得到结果为 right,我们不难得出如下条件判断:
- r 为 null 或者 r === p 或 r === q 时,X 的值为 r 自身,返回 r;
- r 不为 null,left 为 null,此时说明 r 的左子树中不包含 p,q,因此最终结果必然为 right;
- r 不为 null,right 为 null,此时说明 r 的右子树中不包含 p,q,因此最终结果必然为 left;
- r,left,right均 不为 null,此时说明 p,q 分别位列 r 左右子树,此时最终结果应为 r;
通过上面的递归四步的梳理,我们就可以写出最终的答案了。
题解
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root
* @param {TreeNode} p
* @param {TreeNode} q
* @return {TreeNode}
*/
var lowestCommonAncestor = function(root, p, q) {
if (root === null || root === p || root === q) return root;
const left = lowestCommonAncestor(root.left, p, q);
const right = lowestCommonAncestor(root.right, p, q);
if (left === null) return right;
if (right === null) return left;
return root;
};
Q.E.D.