Pythonで左右の部分木が同一となる最大の部分木を見つける方法
問題の概要
二分木が与えられたとき、左の部分木と右の部分木が完全に一致している最大の部分木を見つけることを考えます。望ましい計算量は O(n) です。
例えば、次のような二分木が入力として与えられた場合を考えてみましょう。

この場合、出力は次のようになります。

解法のアプローチ
この問題を解くためには、木をボトムアップ(下から上へ)に走査し、各ノードについて「そのノードを根とする部分木の構造を表す文字列(エンコード)」を作成します。そして、左部分木のエンコードと右部分木のエンコードが一致していれば、そのノードは「左右が同一の部分木」の根であると判断できます。
具体的な手順は以下の通りです。
- solve() 関数を定義します。引数は root(現在のノード)、encode(エンコード文字列を格納するリスト)、maxSize(最大サイズを格納するリスト)、maxNode(該当ノードを格納するリスト)です。
- root が None の場合は 0 を返します。
- left_list と right_list を空文字列を持つリストとして初期化します。
- ls := solve(root.left, left_list, maxSize, maxNode) で左部分木を再帰的に処理します。
- rs := solve(root.right, right_list, maxSize, maxNode) で右部分木を再帰的に処理します。
- size := ls + rs + 1 として、現在の部分木のサイズを計算します。
- left_list[0] と right_list[0] が一致している場合、つまり左右の部分木が同一である場合は、size が maxSize[0] より大きければ maxSize[0] と maxNode[0] を更新します。
- encode[0] に「| 左部分木のエンコード | 現在のノードの値 | 右部分木のエンコード |」という形式で連結していきます。このエンコードによって、親ノードでは子孫の構造全体を比較できるようになります。
- 最後に size を返します。
メインの処理では以下を行います。
- maximum := [0]、encode := [""]、maxNode := [None] を初期化します。
- solve(node, encode, maximum, maxNode) を呼び出します。
- maximum を返します。
実装例
それでは、実際の Python コードを見てみましょう。
class TreeNode:
def __init__(self, data):
self.data = data
self.left = self.right = None
def solve(root, encode, maxSize, maxNode):
if (root == None):
return 0
left_list = [""]
right_list = [""]
ls = solve(root.left, left_list, maxSize, maxNode)
rs = solve(root.right, right_list, maxSize, maxNode)
size = ls + rs + 1
if (left_list[0] == right_list[0]):
if (size > maxSize[0]):
maxSize[0] = size
maxNode[0] = root
encode[0] = encode[0] + "|" + left_list[0] + "|"
encode[0] = encode[0] + "|" + str(root.data) + "|"
encode[0] = encode[0] + "|" + right_list[0] + "|"
return size
def largestSubtree(node, maxNode):
maximum = [0]
encode = [""]
solve(node, encode, maximum, maxNode)
return maximum
root = TreeNode(55)
root.left = TreeNode(15)
root.right = TreeNode(70)
root.left.left = TreeNode(10)
root.left.right = TreeNode(25)
root.right.left = TreeNode(75)
root.right.left.left = TreeNode(65)
root.right.left.right = TreeNode(80)
root.right.right = TreeNode(75)
root.right.right.left = TreeNode(65)
root.right.right.right = TreeNode(80)
maxNode = [None]
maximum = largestSubtree(root, maxNode)
print("Root of largest sub-tree", maxNode[0].data)
print("and its size is", maximum)入力
root = TreeNode(55) root.left = TreeNode(15) root.right = TreeNode(70) root.left.left = TreeNode(10) root.left.right = TreeNode(25) root.right.left = TreeNode(75) root.right.left.left = TreeNode(65) root.right.left.right = TreeNode(80) root.right.right = TreeNode(75) root.right.right.left = TreeNode(65) root.right.right.right = TreeNode(80)
出力
Root of largest sub-tree 70 and its size is [7]
コードの解説
この例では、値 70 を根とするノードに注目してください。その左の子は 75(さらにその下に 65 と 80)、右の子も 75(その下に 65 と 80)となっており、左右の部分木が完全に一致しています。この部分木のサイズは 7 ノードなので、これが答えとなります。
ポイント解説
- リストを使った参照渡し: Python の整数や文字列はイミュータブル(変更不可)なため、再帰の中で値を共有するにはリストなどのミュータブルなオブジェクトを使うのが定石です。ここでは maxSize、maxNode、encode をそれぞれ要素 1 つのリストとして渡しています。
- エンコードによる構造比較: 各ノードのサブツリーを「| 値 |」の形式で文字列化していくことで、単なる値の一致ではなく、構造を含めた完全な一致を判定できます。
- 計算量: 各ノードを一度だけ訪問するため、時間計算量は O(n) です。ただし、エンコード文字列の連結により、最悪の場合 O(n²) のメモリ・時間がかかる可能性がある点には注意が必要です。より厳密に O(n) にしたい場合は、ハッシュ化や部分木 ID の利用が有効です。
-
Pythonで二分木における最大の完全部分木を見つける方法
問題の概要 二分木が与えられたとき、その木の中に含まれる最大の完全部分木(コンプリート・サブツリー)のサイズを求めることを考えます。 ここでいう完全二分木とは、最下層を除くすべてのレベルがノードで完全に埋め尽くされており、最下層のノードは可能な限り左側に配置されている二分木のことです。 たとえば、次のような二分木が入力された場合を考えてみます。 このとき出力されるサイズは 4 となり、最大の完全部分木を通りがけ順(中順)で走査すると 10, 45, 60, 70, の順に出力されます。 解き方のアプローチ この問題は、木を再帰的にたどりながら、各部分木が「完全(complete)」であるか「
-
Pythonで二分木から最大の完全二分木(パーフェクトサブツリー)を見つける方法
与えられた二分木の中から、最大の完全二分木(Perfect Binary Tree)となっているサブツリーを見つける問題を考えてみましょう。完全二分木とは、すべての内部ノードが必ず2つの子を持ち、すべての葉ノードが同じ深さに位置する二分木のことです。例えば、次のような二分木が入力として与えられた場合を想定します。この場合の出力は 3 となり、見つかったサブツリーは次の通りです。解法のアプローチこの問題は、木を再帰的にたどりながら、各部分木について「完全二分木であるかどうか」と「高さ」を記録していくことで効率的に解けます。具体的な手順は以下の通りです。isPerfect(完全二分木かどうか)、h