在求职面试中我被问到以下问题:
给定一个根节点(到一个格式良好的二叉树)和另外两个节点(保证在树中,并且也是不同的),返回两个节点的最低共同祖先。
我不知道任何最不常见的祖先算法,所以我试着在现场制作一个。我制作了以下代码:
def least_common_ancestor(root, a, b):
lca = [None]
def check_subtree(subtree, lca=lca):
if lca[0] is not None or subtree is None:
return 0
if subtree is a or subtree is b:
return 1
else:
ans = sum(check_subtree(n) for n in (subtree.left, subtree.right))
if ans == 2:
lca[0] = subtree
return 0
return ans
check_subtree(root)
return lca[0]
class Node:
def __init__(self, left, right):
self.left = left
self.right = right
我尝试了以下测试用例并得到了我期望的答案:
a = Node(None, None)
b = Node(None, None)
tree = Node(Node(Node(None, a), b), None)
tree2 = Node(a, Node(Node(None, None), b))
tree3 = Node(a, b)
但我的采访者告诉我“有一类树,你的算法会返回无。”我无法弄清楚它是什么,我接受了采访。我想不出这样一种情况,即如果ans
没有变成2,算法会将它移到树的底部 - 我错过了什么?
答案 0 :(得分:3)
您忘了考虑a
是b
的直接祖先的情况,反之亦然。一找到任何一个节点并返回1
,就会停止搜索,因此在这种情况下你永远不会找到另一个节点。
您获得了格式良好的二叉搜索树;这种树的一个属性是你可以根据它们与当前节点的相对大小轻松找到元素;较小的元素进入左子树,较大的元素进入右侧。因此,如果您知道两个元素都在树中,您只需要比较密钥;只要找到在两个目标节点之间的节点,或者等于一个节点,就会找到最低的共同祖先。
您的示例节点从未包含存储在树中的密钥,因此您无法使用此属性,但如果您 ,则使用:
def lca(tree, a, b):
if a.key <= tree.key <= b.key:
return tree
if a.key < tree.key and b.key < tree.key:
return lca(tree.left, a, b)
return lca(tree.right, a, b)
如果树只是一个普通的树。二叉树,而不是搜索树,您唯一的选择是找到两个元素的路径,并找到这些路径发散的点。
如果您的二叉树维护父引用和深度,则可以有效地完成此操作;简单地走到两个节点的深处,直到你处于相同的深度,然后从两个节点向上继续,直到你找到一个公共节点;这是最不常见的祖先。
如果您没有这两个元素,那么您必须从根开始找到两个节点的路径并进行单独搜索,然后找到这两个路径中的最后一个公共节点。
答案 1 :(得分:1)
您错过了a
是b
的祖先的情况。
看看简单的反例:
a
b None
a
也以root
的形式提供,在调用该函数时,您调用check_subtree(root)
,即a
,然后您会发现这就是您所在的寻找(在返回1的stop子句中),并在不设置1
的情况下立即返回lca
。