0
点赞
收藏
分享

微信扫一扫

leetcode:236. 二叉树的最近公共祖先(JavaScript)

老榆 2022-04-26 阅读 58

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

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

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

示例 1:

img

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

示例 2:

img

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

示例 3:

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

提示:

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

思路:采用回溯的思想,而递归自带回溯特性,所以递归很适合解这题。

分析:

记录当前节点的左子树,右子树有没有找到目标节点和当前节点是否是目标节点,因为题目中给出一个节点也可以是他自己的祖先节点,那么就有如下四种情况:

  1. 当前节点是目标节点且左子树或右子树也找到目标节点,说明当前节点就是最近父节点,记录答案
  2. 当前节点不是目标节点,但左右子树中俊找到目标节点,说明当前节点就是最近父节点,记录答案
  3. 当前节点,左子树,右子树中只找到一个目标节点,返回true
  4. 一个都没找到,返回false
if (isTrue && (isLeftTrue || isRightTrue))res = root;
        // 当前节点不是目标节点,但左右子树中俊找到目标节点
        if (isLeftTrue && isRightTrue) res = root; 
        // 当前节点,左子树,右子树中找到一个目标节点
        if (isTrue || isLeftTrue || isRightTrue) return true;
        // 一个都没找到
        else return false;

完整代码

var lowestCommonAncestor = function(root, p, q) {
    let res = null;
    let dfs = function(root, p , q) {
        // 递归终止条件
        if (!root) return false;
        // 记录左子树是否找到
        const isLeftTrue = dfs (root.left, p, q);
        // 记录当前节点是否是目标节点
        let isTrue = false;
        if (root === p || root === q) {
            isTrue = true;
        } 
        // 记录右子树是否找到
        const isRightTrue = dfs (root.right, p ,q);
        // console.log(isRightTrue,isTrue,isLeftTrue,res, root.val)
        // 当前节点是目标节点且左子树或右子树也找到目标节点
        if (isTrue && (isLeftTrue || isRightTrue))res = root;
        // 当前节点不是目标节点,但左右子树中俊找到目标节点
        if (isLeftTrue && isRightTrue) res = root; 
        // 当前节点,左子树,右子树中找到一个目标节点
        if (isTrue || isLeftTrue || isRightTrue) return true;
        // 一个都没找到
        else return false;
    }
    dfs (root, p ,q);
    return res;
};

写完之后感觉代码有点繁琐,所以又仔细思考一下,精简了一下代码如下

var lowestCommonAncestor = function(root, p, q) {
    // 递归终止条件
    if (root === p || root === q || !root) {
        return root;
    } 
    // 记录左子树是否找到
    const isLeftTrue = lowestCommonAncestor (root.left, p, q);
    // 记录右子树是否找到
    const isRightTrue = lowestCommonAncestor (root.right, p ,q);
    // 左右子树都找到目标节点
    if (isLeftTrue && isRightTrue)return root;
    // 左子树中未找到,右子树中俊找到目标节点
    if (!isLeftTrue && isRightTrue) return isRightTrue; 
    // 左子树找到一个目标节点,右子树中未找到
    if (isLeftTrue || !isRightTrue) return isLeftTrue;
    // 一个都没找到
    else return null;
};

以上代码有一个难点就是递归终止条件,不容易想到。

难点1:根据递归回溯的值,有以下四种情况:

  1. 左右子树返回值都不为空,则当前节点就是最近祖先节点。
  2. 左子树返回值为空,右子树返回值不为空,返回左子树的返回值。
  3. 右子树返回值为空,左子树返回值不为空,返回右子树的返回值。
  4. 左右子树返回值都为空,说明左右子树均未找到目标值,则返回null。

难点2:为什么找到目标节点就立即返回该节点呢?

我们来分析分析,当找到一个目标节点后,此时另一个目标节点有两种情况(注意题目说,p,q两节点一定在二叉树上):

  1. 在当前目标节点的子树上,即当前节点就是最近祖先节点,那么返回当前节点合理。
  2. 是当前节点的兄弟节点,那么返回当前节点再由回溯的返回值判断逻辑判断。

这里需要思考一下,怎么判断这两种情况呢?其实在难点1中的回溯四种情况中就悄悄判断了,若当前节点就是最近祖先节点,那么在回溯的过程中就会一直走难点1中2或3这两种选择,最终返回给根节点递归结束返回答案;若另一个目标节点是当前节点的兄弟节点,那么在回溯的过程中一定会走一次难点1中的情况1,之后或许再走情况2或情况3,最终也能返回最终正确答案。这里不懂得话,需要反复思考,最好能自己画一画流程,就明白了。

注意:这里不会出现另一个目标节点是当前目标节点的祖先节点的情况,因为如果祖先节点的话,我们会先遍历到它,并直接返回该节点了,就不会遍历到当前目标节点了。

举报

相关推荐

0 条评论