Pythonで二分木からノードを削除し、残りのフォレストの根を求める方法
本記事では、二分木から特定のノードを削除した際に生じる「フォレスト(森)」の根を求めるアルゴリズムを、Pythonのコードとともにわかりやすく解説します。
問題の概要
二分木の根(root)が与えられ、木に含まれる各ノードは一意の値を持っているものとします。ここで、to_delete リストに含まれる値を持つノードをすべて削除すると、木はいくつかの独立した部分木、すなわち「フォレスト」へと分割されます。このとき、残ったフォレストを構成する各木の根を見つけるのが目的です。
たとえば、次のような二分木が入力として与えられたとします。

このとき to_delete 配列が [3, 5] である場合、値3と5のノードが削除され、出力は次のようになります。

解法のアプローチ
この問題は、深さ優先探索(DFS)による再帰処理を使うことで効率的に解けます。手順は以下の通りです。
- 結果を格納するための配列
resを定義します。 - 再帰用のメソッド
solve()を定義します。引数は「現在のノード」「to_delete配列」「そのノードが根であるかどうかを示すブール値is_root」です。 - ノードが
nullの場合はnullを返します。 - フラグ
flagを、ノードの値がto_deleteに含まれている場合にtrueとなるよう設定します。 flagがfalseかつis_rootがtrueの場合、そのノードをresに追加します。- 左の子に対して
solve(左の子, to_delete, flag)を呼び出します。 - 右の子に対して
solve(右の子, to_delete, flag)を呼び出します。 flagが立っている場合はNoneを返し、そうでなければノード自身を返します。- メインメソッドからは
solve(root, to_delete, True)の形で呼び出します。
ポイントは、親ノードが削除対象だった場合、その子ノードは新たなフォレストの根の候補になるという点です。flag を子ノードへの is_root 引数として渡すことで、この関係を自然に表現できます。
実装例
class Solution(object):
def delNodes(self, root, to_delete):
"""
:type root: TreeNode
:type to_delete: List[int]
:rtype: List[TreeNode]
"""
to_delete = set(to_delete)
self.res = []
self.solve(root, to_delete, True)
return self.res
def solve(self, node, to_delete, is_root):
if not node:
return None
flag = node.val in to_delete
if not flag and is_root:
self.res.append(node)
node.left = self.solve(node.left, to_delete, flag)
node.right = self.solve(node.right, to_delete, flag)
return None if flag else nodeコードのポイント
to_delete をリストから set に変換している点に注目してください。Pythonのセットは要素の存在確認が平均O(1)で行えるため、各ノードごとの判定が高速になります。
また、このアルゴリズムは各ノードを一度だけ訪問するため、時間計算量はO(n)、空間計算量も再帰の深さにより最悪O(n)となります。
動作確認
入力
[1,2,3,4,5,6,7] [3,5]
出力
[[1,2,null,4],[6],[7]]
値3と5のノードが削除され、残った3つの木の根 [1,2,null,4]、[6]、[7] が正しく返されていることが確認できます。
-
Pythonで二分木の葉ノードと非葉ノードの数を求めるプログラム
二分木が与えられたとき、最初の要素に葉ノード(リーフノード)の数、2番目の要素に非葉ノードの数を格納した2つの数値のペアを求める問題を考えてみましょう。例えば、次のような二分木が入力として与えられた場合を考えます。この木には葉ノードが3つ、非葉ノードが2つ存在するため、出力は (3, 2) となります。解き方のアルゴリズムこの問題は、再帰処理を使って以下の手順で解くことができます。ノード n が null(None)である場合は、(0, 0) を返します。n の左の子と右の子がどちらも null の場合(つまり n が葉ノードの場合)は、(1, 0) を返します。left := solve(n
-
Pythonで先行順(プレオーダー)と後行順(ポストオーダー)から二分木を構築する方法
はじめに二分木の復元問題は、コーディング面接でも頻出のテーマです。本記事では、先行順トラバーサル(プレオーダー)と後行順トラバーサル(ポストオーダー)という2つの走査結果が与えられたときに、元の二分木を再構築する方法をPythonで解説します。たとえば、先行順が [1,2,4,5,3,6,7]、後行順が [4,5,2,6,7,3,1] の場合、次のような二分木が得られます。一意な復元に関する注意点まず押さえておきたいのは、先行順と後行順の組み合わせだけでは、すべての内部ノードが2つの子を持つ場合に限り木が一意に決定されるという点です。子を1つしか持たないノードが存在すると、その子が左側なのか右