[路飞]_leetcode刷题_面试题 04.08. 首个共同祖先

177 阅读4分钟

「这是我参与2022首次更文挑战的第20天,活动详情查看:2022首次更文挑战

题目

面试题 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。因为根据定义最近公共祖先节点可以为节点本身。

解法

二叉树的后序遍历。

思路

使用二叉树的后序遍历,去左右子树里找p和q节点。

p或q的分布,有三种可能

  1. p和q在子树两侧
  2. p和q在同侧

对于根节点root来说,那么会有三种情况发生。

  1. 在左右子树里分别找到了p和q
  2. 在左子树或右子树里找到了p和q
  3. 根节点即为p或q,那就不用继续找了,根节点就是最近的祖先。

我们再用递归的眼光去看这个问题,假设是一个完整二叉树,好理解一点。对于根节点,我们会有如下判断和操作

  1. 如果当前根节点不是p或者q,那么会一直找下去,直到找到左叶子节点。
  2. 由于左叶子节点的左右子节点都是null,没办法找了,那么我们就返回null,代表在当前这个叶子节点没有找到p或q。
  3. 然后我们去找右叶子节点,如果也没找到,同理,也返回null。
  4. 这个时候就代表对于左叶子节点和右叶子节点的根节点,在它的子树里没有找到p或者q,所以它也返回null。

所以我们知道,在遍历过程中,在某个节点的子树里如果没有找到p或者q,那么就返回null,如果找到了p或q,那么返回的就是p或者q节点。一旦在某个节点的子树找到了,对于该节点的所有祖先节点来说,返回值都不会是null。

另外还有一点,如果在某一个节点找到了p或q,其实遍历也没必要往下继续,因为既然能够到这个节点来,那么它的祖先都不是p或者q,那么p、q中的另一个一定在它的子树,或者对于它的父节点所在的子树的另一侧子树中。

代码

/**
 * 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) {
    // 后序遍历递归该树
    return postOrder(root,p,q);
};

function postOrder(root, p, q) {
    if(root == null){
        return null;
    }
    // 如果当前节点就是p或q,则直接返回当前节点
    if(root == p || root == q){
        return root;
    }
    // 去左子树里找,一直找到叶子节点,如果找到了,则该left代表找到的那个p或q
    let left = postOrder(root.left,p,q);
    // 同理,去右子树里找
    let right = postOrder(root.right,p,q);
    // 以下分4中情况
    // 1.左右都找到了p或q,那么当前节点就是最近祖先
    if(left != null && right != null){
        return root;
    // 2. 左子树找到了,右子树没找到,则目前来说,该节点是最近祖先
    }else if(left != null){
        return left;
    // 3. 右子树找到了,左子树没找到,则目前来说,该节点是最近祖先
    }else if(right !=null){
        return right;
    // 两侧都没找到,说明该节点子树未找到,则返回null
    }else{
        return null;
    }
    
}

复杂度分析

时间复杂度:O(n),最坏情况要将所有节点都遍历一遍。

空间复杂度:O(n),最坏情况二叉树为链表,且两个节点在最末端,那么需要将所有节点遍历一遍,空间复杂度为二叉树的高度,即为n。