leetcode
leetcode 1501 ~ 1550
二叉树的最近公共祖先 II

二叉树的最近公共祖先 II

难度:

标签:

题目描述

代码结果

运行时间: 124 ms, 内存: 20.3 MB


/*
 * Leetcode 1644: Lowest Common Ancestor of a Binary Tree II using Java Streams
 * 
 * 思路:
 * 使用流的方式不适合解决树的递归问题,因为流更适合处理集合,而树的结构要求递归处理。此处依然使用递归方法,只是展示如何在树的遍历过程中使用流处理集合。
 */

public class Solution {
    private boolean foundP = false;
    private boolean foundQ = false;
    
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        TreeNode lca = findLCA(root, p, q);
        if (foundP && foundQ) {
            return lca;
        }
        return null;
    }
    
    private TreeNode findLCA(TreeNode root, TreeNode p, TreeNode q) {
        if (root == null) {
            return null;
        }
        TreeNode left = findLCA(root.left, p, q);
        TreeNode right = findLCA(root.right, p, q);
        if (root == p) {
            foundP = true;
            return root;
        }
        if (root == q) {
            foundQ = true;
            return root;
        }
        if (left != null && right != null) {
            return root;
        }
        return left != null ? left : right;
    }
}

class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode(int x) { val = x; }
}

解释

方法:

这个解法使用了递归的方式来查找二叉树的最近公共祖先。递归函数 `helper` 从根节点开始,对二叉树进行后序遍历(即先探索子节点,再处理当前节点)。在探索过程中,当到达叶节点或已找到最近公共祖先时,递归会回溯。在每个节点,函数检查其左右子节点是否包含节点 p 或 q。如果当前节点自身是 p 或 q,或其任一子树包含 p 或 q,该节点会向其父节点报告这一发现。如果一个节点的两个子树各包含一个目标节点(p 或 q),那么该节点就是最近的公共祖先。为保存这个结果,使用了一个非局部变量 `res`。当找到最近公共祖先后,无需进一步递归,因此可以提前结束递归过程。

时间复杂度:

O(n)

空间复杂度:

O(h)

代码细节讲解

🦆
在递归函数`helper`中,返回值是如何帮助确定最近公共祖先的?具体的返回逻辑是怎样的?
在`helper`函数中,返回值用于表示当前节点或其子树是否包含目标节点p或q。如果当前节点是p或q,或者其左右子树中至少有一边含有p或q,则返回当前节点。这样,如果一个节点的左右子树分别返回p和q(表示两边各含有一个目标节点),那么当前节点即为p和q的最近公共祖先。返回逻辑具体为:1. 如果当前节点为空或已找到最近公共祖先,返回null;2. 递归检查左右子树;3. 如果左右子树各返回一个目标节点,或者当前节点是p或q且其一边子树包含另一个目标节点,则当前节点为公共祖先;4. 否则,返回包含p或q的子树。
🦆
函数中使用了`nonlocal res`关键字,能否详细解释为什么需要使用`nonlocal`以及它的作用是什么?
`nonlocal res`关键字用于在`helper`内部函数中访问并修改外部函数`lowestCommonAncestor`中定义的变量`res`。在Python中,内部函数默认不能修改外部函数的局部变量,除非声明为`nonlocal`。这允许`helper`函数在发现最近公共祖先时直接修改`res`的值,保持最近公共祖先的状态跨多次递归调用一致。
🦆
你提到如果`当前节点自身是 p 或 q,或其任一子树包含 p 或 q,该节点会向其父节点报告这一发现`,这是如何通过代码实现的?具体在哪一部分代码中反映?
这一逻辑通过递归函数`helper`返回值实现。当递归到一个节点,首先检查其左右子树是否包含p或q(通过调用`helper(node.left)`和`helper(node.right)`)。如果任一子树返回非null(即找到p或q),或者当前节点自身是p或q,则通过`return node`将这个节点返回给调用它的父节点。这样,每个父节点都能知道其子节点是否包含p或q。具体反映在代码中的`if node is p or node is q`和`return lfound or rfound`部分。
🦆
递归函数在发现`左右子树各找到一个目标节点`时就会设置`res`为当前节点并返回,这个逻辑是否意味着递归调用会在找到答案后立即停止?还是会继续执行其他分支的递归调用?
当发现左右子树各找到一个目标节点时,`res`被设置为当前节点。然而,递归调用并不会立即全局停止,因为当前递归路径的返回并不会停止其他正在执行的递归调用。虽然`res`已经设置,但其他分支的递归调用仍会继续执行至它们自然结束或检测到`res`非空而提前返回。这意味着虽然最近公共祖先已经找到,但程序可能仍在执行其他不必要的递归调用。

相关问题