[路飞] 首个共同祖先

95 阅读2分钟

记录 1 道算法题

首个共同祖先

leetcode-cn.com/problems/fi…


要求:给一个二叉树和其中两个节点,返回这两个节点的最近共同祖先。

相关题目:二叉搜索树的最近公共祖先

要找公共的祖先只能遍历一遍二叉树,使用后序遍历的话是从下向上遍历二叉树,也比较比较符合找东西的思维,看什么时候合流。

关键是递归的返回值,递归只有一个返回值,另外我们要如何根据这个返回值继续计算和向上返回。

首先找到这个公共节点后怎么存,能不能返回节点?假设后序遍历我们在匹配上的时候将这个节点的父亲也就是 root 返回。这个返回值会作为上一层递归的 left 或者 right,这时候 left 和 right 变成不匹配,也就是说没办法将是否匹配的结果一路返回。

那么怎么返回匹配成功的结果呢?

true 和 false 可以明确知道有没有返回,如果返回了 true 说明子节点已经匹配上了。但是如何传递祖先节点呢,可以采用闭包的方式。

确定方式后继续讨论,什么是祖先节点。

祖先节点有两种状态,一种是从左右两边汇聚上来的祖先节点。另一种是节点自身是祖先节点。

当返回值都是 true 的时候,毋庸置疑是第一种情况,那这时候 root 就是祖先节点,只需要保存 root 就可以了。

当返回值有一个是 true, 有一个是 false 的时候,这时候是两个节点的高度不一样,两种情况都有可能。这时候就要一直匹配 root 和提供的两个节点,什么时候匹配上,什么时候就确定祖先节点。返回值如果是 true 要进行穿透。

    function lowestCommonAncestor(root, p, q) {
        // p, q 以传参的方式性能会好
        function dfs(root, p, q) {
            if (!root) return false
            // 后序遍历
            const left = dfs(root.left, p, q)
            const right = dfs(root.right, p, q)
            
            if (left && right) {
                ans = root
                return true
            }
            if (left) {
                // 因为可能是同一边的节点,所以都要比较
                if (root.val === p.val || root.val === q.val) {
                    // 匹配上了说明自身是祖先节点
                    ans = root
                }
                return true // 穿透 left 的结果
            }
            if (right) {
                if (root.val === p.val || root.val === q.val) {
                    // 匹配上了说明自身是祖先节点
                    ans = root
                }
                return true
            }
            // 子节点都没有匹配上,检查自己是否匹配
            return root.val === p.val || root.val === q.val
        }
        
        let ans
        dfs(root, p, q)
        return ans
    }

以上的代码可以进行简化,因为有相同的代码。

    // 其他地方一样,dfs函数可以精简
    function dfs(root, p, q) {
        if (!root) return false
        // 后序遍历
        const left = dfs(root.left, p, q)
        const right = dfs(root.right, p, q)
        
        if (left && right || ((left || right) && (root.val === p.val || root.val === q.val)) {
            ans = root
        }
        // 穿透结果或检查 root
        return left || right || (root.val === q.val || root.val === p.val)
    }