leetcode236.二叉树的最近公共祖先:递归后序遍历的祖先追踪之道

一、题目深度解析与核心定义

题目描述

在二叉树中找到两个节点pq的最近公共祖先(LCA)。最近公共祖先是指两个节点的所有祖先中距离它们最近的那个节点。二叉树的节点可以包含任意值,且不一定是搜索树,因此无法利用值的大小关系,只能通过树的结构遍历求解。

核心性质

  1. 递归定义:对于当前节点root,若rootpq,或者pq分别在root的左右子树中,则root是LCA。
  2. 后序遍历特性:从底向上查找,先处理子树再处理当前节点,便于判断子树中是否存在目标节点。

示例说明

输入二叉树:

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

p=5,q=4
输出:5
LCA是5,因为5是包含p和q的最底层祖先。

二、递归解法的核心实现与逻辑框架

完整递归代码实现

/**
 * 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) {
        // 终止条件:当前节点为空,或当前节点是p或q
        if (root == null || root == p || root == q) {
            return root;
        }
        // 递归查找左子树和右子树
        TreeNode left = lowestCommonAncestor(root.left, p, q);
        TreeNode right = lowestCommonAncestor(root.right, p, q);
        
        // 根据左右子树的返回结果判断当前节点是否为LCA
        if (left == null && right == null) { // 左右子树都没找到,返回null
            return null;
        } else if (left != null && right == null) { // 只有左子树找到,返回左子树结果
            return left;
        } else if (left == null && right != null) { // 只有右子树找到,返回右子树结果
            return right;
        } else { // 左右子树都找到,说明当前节点是LCA
            return root;
        }
    }
}

核心变量与递归逻辑

  1. 终止条件

    • root == null:空节点,无祖先
    • root == p || root == q:当前节点是其中一个目标节点,向上返回作为候选祖先
  2. 递归过程

    • 先递归左子树和右子树,获取左右子树中是否存在目标节点或祖先
    • 根据左右返回结果,判断当前节点是否为LCA

三、核心问题解析:递归逻辑与祖先判定

1. 递归终止条件的设计

if (root == null || root == p || root == q) {
    return root;
}
  • 空节点处理:递归到底层节点,返回null作为无效祖先
  • 目标节点匹配:当找到p或q时,向上传递该节点,作为祖先查找的起点

2. 后序遍历的祖先判定逻辑

四种返回情况分析:
  1. 左右都为空(left == null && right == null)

    • 左右子树都不存在p或q,当前子树无祖先,返回null
  2. 左存在右不存在(left != null && right == null)

    • 左子树中存在p或q(或其祖先),右子树无,返回左子树结果
  3. 右存在左不存在(left == null && right != null)

    • 右子树中存在p或q(或其祖先),左子树无,返回右子树结果
  4. 左右都存在(left != null && right != null)

    • p和q分别在左右子树中,当前节点是最近公共祖先,返回root
逻辑本质:
  • 后序遍历保证从叶子节点开始向上判断,当左右子树都能找到目标节点时,当前节点必然是它们的最近公共祖先

四、算法复杂度分析

1. 时间复杂度

  • O(n):每个节点最多访问一次,n为树的节点数
  • 递归过程中每个节点执行常数级判断,总时间线性于节点数

2. 空间复杂度

  • O(h):h为树的高度(递归栈深度)
    • 平衡树:h=logn,空间复杂度O(logn)
    • 最坏情况(链表树):h=n,空间复杂度O(n)

五、核心技术点总结:递归解法的三大关键

1. 后序遍历的逆向追踪

  • 自底向上:从叶子节点开始判断,逐步向上汇聚结果
  • 路径压缩:无需记录路径,通过递归返回值直接确定祖先

2. 终止条件的双重作用

  • 空节点过滤:避免无效递归路径
  • 目标节点捕获:及时返回目标节点,作为祖先查找的起点

3. 结果合并逻辑

  • 左右子树结果分析
    • 仅左存在:祖先在左子树
    • 仅右存在:祖先在右子树
    • 左右都存在:当前节点是LCA

六、常见误区与边界处理

1. 错误理解公共祖先定义

  • 误区:认为祖先必须同时是p和q的父节点
  • 正确逻辑:祖先可以是p或q本身(当其中一个节点是另一个的祖先时)

2. 忽略空树处理

  • 边界条件:当root为空时返回null,代码中已通过终止条件处理

3. 优化建议:记忆化搜索

  • 场景:多次查询LCA时可缓存结果
  • 实现:使用哈希表存储节点的父节点,逐层向上查找
  • 对比:递归解法更简洁,适合单次查询

七、总结:递归法求解LCA的本质是后序汇聚

本算法通过递归后序遍历,利用树的层次结构特性,从底向上追踪目标节点,最终在最近公共祖先处汇聚左右子树的结果。核心在于:

  1. 递归终止条件:准确捕获目标节点和空节点,作为递归的起点和边界
  2. 后序处理逻辑:先处理子树再处理当前节点,便于判断祖先关系
  3. 结果合并策略:根据左右子树的返回值,高效判定当前节点是否为LCA

这种递归解法简洁且高效,充分利用了树的递归结构特性,无需额外数据结构,是求解二叉树LCA问题的经典方法。理解其核心逻辑后,可轻松扩展到二叉搜索树等变种问题,体现了递归在树结构问题中的强大表现力。

你可能感兴趣的:(leetcode刷题详解,算法,leetcode,java,数据结构)