难度: Medium
Submission
运行时间: 124 ms
内存: 20.3 MB
# Definition for a binary tree node. # class TreeNode: # def __init__(self, x): # self.val = x # self.left = None # self.right = None class Solution: def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode': res = None def helper(node): nonlocal res if not node or res: return lfound = helper(node.left) rfound = helper(node.right) if lfound and rfound or ((lfound or rfound) and node in (p, q)): res = node return node if node is p or node is q: return node return lfound or rfound helper(root) return res
Explain
这个解法使用了递归的方式来查找二叉树的最近公共祖先。递归函数 `helper` 从根节点开始,对二叉树进行后序遍历(即先探索子节点,再处理当前节点)。在探索过程中,当到达叶节点或已找到最近公共祖先时,递归会回溯。在每个节点,函数检查其左右子节点是否包含节点 p 或 q。如果当前节点自身是 p 或 q,或其任一子树包含 p 或 q,该节点会向其父节点报告这一发现。如果一个节点的两个子树各包含一个目标节点(p 或 q),那么该节点就是最近的公共祖先。为保存这个结果,使用了一个非局部变量 `res`。当找到最近公共祖先后,无需进一步递归,因此可以提前结束递归过程。
时间复杂度: O(n)
空间复杂度: O(h)
# Definition for a binary tree node. # class TreeNode: # def __init__(self, x): # self.val = x # self.left = None # self.right = None class Solution: def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode': res = None # 用于存储最近公共祖先的节点 def helper(node): nonlocal res # 允许访问函数外的 `res` 变量 if not node or res: # 如果节点为空或已找到结果,则停止递归 return lfound = helper(node.left) # 检查左子树中是否有 p 或 q rfound = helper(node.right) # 检查右子树中是否有 p 或 q # 如果左右子树各找到一个目标节点或当前节点是目标节点之一,设置当前节点为结果 if lfound and rfound or ((lfound or rfound) and node in (p, q)): res = node return node if node is p or node is q: # 如果当前节点是 p 或 q,返回当前节点 return node return lfound or rfound # 返回找到的目标节点 helper(root) return res
Explore
在`helper`函数中,返回值用于表示当前节点或其子树是否包含目标节点p或q。如果当前节点是p或q,或者其左右子树中至少有一边含有p或q,则返回当前节点。这样,如果一个节点的左右子树分别返回p和q(表示两边各含有一个目标节点),那么当前节点即为p和q的最近公共祖先。返回逻辑具体为:1. 如果当前节点为空或已找到最近公共祖先,返回null;2. 递归检查左右子树;3. 如果左右子树各返回一个目标节点,或者当前节点是p或q且其一边子树包含另一个目标节点,则当前节点为公共祖先;4. 否则,返回包含p或q的子树。
`nonlocal res`关键字用于在`helper`内部函数中访问并修改外部函数`lowestCommonAncestor`中定义的变量`res`。在Python中,内部函数默认不能修改外部函数的局部变量,除非声明为`nonlocal`。这允许`helper`函数在发现最近公共祖先时直接修改`res`的值,保持最近公共祖先的状态跨多次递归调用一致。
这一逻辑通过递归函数`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`非空而提前返回。这意味着虽然最近公共祖先已经找到,但程序可能仍在执行其他不必要的递归调用。