【二叉树】二叉树的最近公共祖先

本文详细解析了二叉树中寻找最近公共祖先的两种方法:递归法和路径标记法,包括每种方法的基本思路、时间复杂度和空间复杂度分析。递归法通过深度优先搜索解决问题,路径标记法则利用DFS记录节点路径,找到两条路径的交点作为最近公共祖先。

一、题目

力扣原题:https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/

二、递归

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {

    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if (null == root || null == p || null == q) {
            return null;
        }

        // 若当前节点为p节点或q节点,则当前节点即“最近公共祖先”,直接返回即可(剪枝)
        if (root.val == p.val || root.val == q.val) {
            return root;
        }

        // 判断左子树是否存在p节点或q节点
        TreeNode left = null;
        if (null != root.left) {
            left = lowestCommonAncestor(root.left, p, q);
        }

        // 判断右子树是否存在p节点或q节点
        TreeNode right = null;
        if (null != root.right) {
            right = lowestCommonAncestor(root.right, p, q);
        }

        // 若左右子树均存在p节点或q节点,则当前节点即“最近公共祖先”
        if (null != left && null != right) {
            return root;
        }

        // 若左子树或右子树存在p节点或q节点,则此节点即“最近公共祖先”,直接返回即可
        // 若左右子树均不存在p节点或q节点,则不存在“最近公共祖先”,返回null
        return left != null ? left : right;
    }

}
  • 基本思路:
    • 当前节点为p节点或q节点时,当前节点即“最近公共祖先”;
    • 当前节点的左子树、右子树均存在p节点或q节点时,说明两个目标节点在当前节点的两侧,当前节点即“最近公共祖先”;
    • 当前节点的左子树或右子树存在p节点或q节点,则此节点即“最近公共祖先”;
    • 当前节点的左右子树均不存在p节点或q节点,说明不存在“最近公共祖先”;
  • 时间复杂度:本质是DFS深搜整棵二叉树,查找p节点和q节点
    • 最优:O(1)。根节点即p节点或q节点,此时根节点即“最近公共祖先”,无需递归左右子树;
    • 最差:O(n)。遍历二叉树的所有节点;
    • 平均:O(n)。
  • 空间复杂度:主要体现在递归的栈空间消耗,栈的深度即二叉树的深度
    • 最优:O(log(n + 1))。最好的情况下,除了最底层,其他层节点的左右子树均不为空,此时二叉树具有最小深度;
    • 最差:O(n)。退化为链表时;
    • 平均:O(log(n));

二、路径标记法

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {

    // 保存所有节点的父节点
    private Map<TreeNode, TreeNode> map;

    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if (null == root || null == p || null == q) {
            return null;
        }

        // 深搜二叉树,保存所有节点的父节点
        map = new HashMap<TreeNode, TreeNode>();
        dfs(root);

        // 从p节点开始,标记其所有的直接、间接父节点
        Set<TreeNode> set = new HashSet<>();
        while (null != p) {
            set.add(p);
            p = map.get(p);
        }

        // 从q节点开始,访问其所有直接、间接父节点,并判断其标记位
        while (null != q) {
            if (set.contains(q)) {
                return q;
            }
            q = map.get(q);
        }
        return null;
    }

    private void dfs(TreeNode node) {
        // 递归遍历左子树
        if (null != node.left) {
            map.put(node.left, node);
            dfs(node.left);
        }

        // 递归遍历右子树
        if (null != node.right) {
            map.put(node.right, node);
            dfs(node.right);
        }
    }
}
  • 基本思路:
    • 通过一轮DFS深搜二叉树,记录每个节点的直接父节点,存储在Map中;
    • 由p节点开始,访问其所有的直接、间接父节点,并通过Set标记出来(p节点到root节点的路径);
    • 由q节点开始,访问其所有的直接、间接父节点,并判断当前节点是否被标记过(q节点到root节点的路径);
    • “最近公共祖先”即p节点到root节点、q节点到root节点这两条路径的交点;
  • 时间复杂度:O(n)。DFS深搜遍历二叉树的时间复杂度为O(n),p节点、q节点路径遍历的时间复杂度均小于O(n),因此时间复杂度为O(n)。
  • 空间复杂度:O(n)。DFS深搜的空间复杂度等于二叉树的高度,最优为O(log(n)),最差为O(n)。存储所有节点的直接父节点的Map结构的空间复杂度为O(n),因此空间复杂度为O(n)。

四、总结

  • 二叉树的遍历问题可以通过DFS深搜解决;
  • 二叉树问题经常可以转化为递归问题(解法一);
  • 根据题意,通过绘图我们可以发现p节点、q节点的“最近公共祖先”即是这两个节点到root节点路径的交点。但将这个思想转化为程序表达的技巧实在是巧妙!
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值