[前端]_一起刷leetcode 面试题 04.08. 首个共同祖先

114 阅读3分钟

大家好,我是挨打的阿木木,爱好算法的前端摸鱼老。最近会频繁给大家分享我刷算法题过程中的思路和心得。如果你也是想提高逼格的摸鱼老,欢迎关注我,一起学习。

题目

面试题 04.08. 首个共同祖先

设计并实现一个算法,找出二叉树中某两个节点的第一个共同祖先。不得将其他的节点存储在另外的数据结构中。注意:这不一定是二叉搜索树。

例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4]

    3
   / \
  5   1
 / \ / \
6  2 0  8
  / \
 7   4

示例 1:

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

示例 2:

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

说明:

所有节点的值都是唯一的。
pq 为不同节点且均存在于给定的二叉树中。

思路

  1. 递归找到pq节点,并记录它们的所有祖先节点;
  2. 找到p节点的所有祖先节点,用set记录下来;
  3. 用队列存储q节点的所有祖先节点,一个个取出来判断是否存在set中;
  4. 最终它们总能找到公共祖先节点,因为树中只有一个根节点,所以不需要做特殊处理。

实现

/**
 * 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) {
    let set = new Set();
    let queue = [];

    // 寻找P节点,把所有的祖先构建成Set对象
    function findPNode(node, p) {
        if (!node) return false;

        if (node === p || findPNode(node.left, p) || findPNode(node.right, p)) {
            set.add(node);
            return true;
        }

        return false;
    }

    // 寻找Q节点,把所有的祖先构建成队列
    function findQNode(node, q) {
        if (!node) return false;

        if (node === q || findQNode(node.left, q) || findQNode(node.right, q)) {
            queue.push(node);
            return true;
        }

        return false;
    }

   
    // 找到P节点的所有祖先,记录下来
    findPNode(root, p);
    // 找到Q节点的所有祖先
    findQNode(root, q);

    // 找交集
    while (queue.length) {
        const cur = queue.shift();
        if (set.has(cur)) {
            return cur;
        }
    }
};

优化

以上两个遍历的过程,我们可以合并成同一个,我们只需要用两个变量来分别记录pq节点分别是否已经被找到了即可。

  1. 记录pq节点分别是否已经被找到;

  2. 遍历左子树寻找节点,如果找到了就记录下来;

  3. 遍历完左子树,可能有三种情况:

    3.1 同时找到了两个节点,那么直接返回最后一级根节点即可;

    3.2 两个节点都不存在,那么它们必定在右子树上;

    3.3 找到了一个节点,那么遍历完右子树还是要往上查找。

/**
 * 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) {
    let hasP = false, hasQ = false;

    function dfs(node, p, q) {
        if (!node) return null;

        // 遍历左子树
        let leftResult = dfs(node.left, p, q);
        if (hasP && hasQ) return leftResult;

        // 如果只有一个在左子树上,说明要往上查找
        let hasOne = hasP || hasQ;

        // 遍历右子树
        let rightResult = dfs(node.right, p, q);
        if (hasP && hasQ) return hasOne ? node : rightResult;

        if (node.val === p.val) hasP = true;
        if (node.val === q.val) hasQ = true;

        return hasP && hasQ ? node : null;
    }

    return dfs(root, p, q);
};

看懂了的小伙伴可以点个关注、咱们下道题目见。如无意外以后文章都会以这种形式,有好的建议欢迎评论区留言。