Pythonで二分木の最も深い葉ノードの最小共通祖先(LCA)を求める方法
根付き二分木が与えられたとき、その最も深い葉ノードの最小共通祖先(LCA: Lowest Common Ancestor)を返す問題を考えてみましょう。この問題を解く前に、以下の定義を確認しておきます。
前提となる定義
- 二分木のノードは、子を持たない場合に限り「葉ノード」と呼ばれます。
- 木の根(ルート)の深さは0とし、あるノードの深さがdであるとき、その子ノードの深さはd+1になります。
- ノード集合Sの最小共通祖先とは、S内のすべてのノードがその部分木に含まれるような、最も深いノードAのことです。
たとえば、入力が [1,2,3,4,5] の二分木である場合、出力は [2,4,5] となります。これは、最も深い葉ノードが4と5であり、それらの共通祖先が2だからです。
解法のアプローチ
この問題は、再帰的な後順走査(ポストオーダー)を用いることで効率的に解けます。各ノードについて「その部分木の最大深さ」と「最深葉のLCA候補」をペアで返していくのがポイントです。
solve() メソッドの設計
solve(node)を定義します。このメソッドは [深さ, LCA候補ノード] のリストを返します。- node が存在しない場合は
[0, None]を返します。 - 左右の子がどちらも存在しない(葉ノード)場合は
[1, node]を返します。 - 左部分木の結果を d1, l に、右部分木の結果を d2, r にそれぞれ格納します。
- d1 > d2 の場合は
[d1 + 1, l]を返します(より深い側のLCAを引き継ぐ)。 - d2 > d1 の場合は
[d2 + 1, r]を返します。 - 両者の深さが等しい場合は、現在のノードが共通祖先となるため
[d1 + 1, node]を返します。
メイン処理
list := solve(root)を呼び出します。list[1](LCA候補ノード)を返します。
Pythonでの実装例
以下のコードで実際の動作を確認してみましょう。
class TreeNode:
def __init__(self, data, left = None, right = None):
self.data = data
self.left = left
self.right = right
def insert(temp, data):
que = []
que.append(temp)
while (len(que)):
temp = que[0]
que.pop(0)
if (not temp.left):
if data is not None:
temp.left = TreeNode(data)
else:
temp.left = TreeNode(0)
break
else:
que.append(temp.left)
if (not temp.right):
if data is not None:
temp.right = TreeNode(data)
else:
temp.right = TreeNode(0)
break
else:
que.append(temp.right)
def make_tree(elements):
Tree = TreeNode(elements[0])
for element in elements[1:]:
insert(Tree, element)
return Tree
def print_tree(root):
# 中順走査(inorder traversal)で出力
if root is not None:
print_tree(root.left)
print(root.data, end = ', ')
print_tree(root.right)
class Solution(object):
def lcaDeepestLeaves(self, root):
return self.solve(root)[1]
def solve(self, node):
if not node:
return [0, None]
if not node.left and not node.right:
return [1, node]
d1, l = self.solve(node.left)
d2, r = self.solve(node.right)
if d1 > d2:
return [d1 + 1, l]
elif d2 > d1:
return [d2 + 1, r]
return [d1 + 1, node]
ob = Solution()
root = make_tree([1,2,3,4,5])
print_tree(ob.lcaDeepestLeaves(root))入力
[1,2,3,4,5]
出力
4, 2, 5,
計算量について
このアルゴリズムは各ノードを一度だけ訪問するため、時間計算量はO(n)(nはノード数)となります。また、再帰呼び出しのスタックの深さは木の高さに依存するため、空間計算量は最悪の場合O(n)です。木が平衡に近い場合はO(log n)に抑えられます。
-
Pythonで二分木の最小共通祖先(LCA)を求める方法
二分木が与えられたとき、指定した2つのノードの最小共通祖先(Lowest Common Ancestor:LCA)を求める問題を考えてみましょう。ノード p と q の LCA とは、p と q の両方を子孫として持つノードの中で、最も深い位置にあるノードのことです。 例えば、二分木が [3,5,1,6,2,0,8,null,null,7,4] という形式で表されている場合、木の構造は次のようになります。 この場合、ノード 5 と ノード 1 の LCA は 3 となります。 解法のアプローチ この問題は、再帰を使って次の手順で解くことができます。 木が空(None)の場合は、None
-
Pythonで二分探索木の最小共通祖先(LCA)を求める方法【再帰アルゴリズム解説】
最小共通祖先(LCA)とは二分探索木(Binary Search Tree)が与えられたとき、指定された2つのノードの最小共通祖先(Lowest Common Ancestor:LCA)を求める問題を考えてみましょう。ノード p と q の LCA とは、「p と q の両方を子孫として持つノードの中で、最も深い位置にあるノード」のことです。例えば、次のような二分木があるとします。[6, 2, 8, 0, 4, 7, 9, null, null, 3, 5]この木は以下のような構造になります。この場合、2 と 8 の LCA は 6 となります。6 は 2 と 8 の両方を子孫に持ち、それより