Python
 Computer >> コンピューター >  >> プログラミング >> Python

Pythonで二分木からノードを削除し、残りのフォレストの根を求める方法

本記事では、二分木から特定のノードを削除した際に生じる「フォレスト(森)」の根を求めるアルゴリズムを、Pythonのコードとともにわかりやすく解説します。

問題の概要

二分木の根(root)が与えられ、木に含まれる各ノードは一意の値を持っているものとします。ここで、to_delete リストに含まれる値を持つノードをすべて削除すると、木はいくつかの独立した部分木、すなわち「フォレスト」へと分割されます。このとき、残ったフォレストを構成する各木の根を見つけるのが目的です。

たとえば、次のような二分木が入力として与えられたとします。

Pythonで二分木からノードを削除し、残りのフォレストの根を求める方法

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

Pythonで二分木からノードを削除し、残りのフォレストの根を求める方法

解法のアプローチ

この問題は、深さ優先探索(DFS)による再帰処理を使うことで効率的に解けます。手順は以下の通りです。

  • 結果を格納するための配列 res を定義します。
  • 再帰用のメソッド solve() を定義します。引数は「現在のノード」「to_delete 配列」「そのノードが根であるかどうかを示すブール値 is_root」です。
  • ノードが null の場合は null を返します。
  • フラグ flag を、ノードの値が to_delete に含まれている場合に true となるよう設定します。
  • flagfalse かつ is_roottrue の場合、そのノードを 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] が正しく返されていることが確認できます。

  1. Pythonで二分木の葉ノードと非葉ノードの数を求めるプログラム

    二分木が与えられたとき、最初の要素に葉ノード(リーフノード)の数、2番目の要素に非葉ノードの数を格納した2つの数値のペアを求める問題を考えてみましょう。例えば、次のような二分木が入力として与えられた場合を考えます。この木には葉ノードが3つ、非葉ノードが2つ存在するため、出力は (3, 2) となります。解き方のアルゴリズムこの問題は、再帰処理を使って以下の手順で解くことができます。ノード n が null(None)である場合は、(0, 0) を返します。n の左の子と右の子がどちらも null の場合(つまり n が葉ノードの場合)は、(1, 0) を返します。left := solve(n

  2. Pythonで先行順(プレオーダー)と後行順(ポストオーダー)から二分木を構築する方法

    はじめに二分木の復元問題は、コーディング面接でも頻出のテーマです。本記事では、先行順トラバーサル(プレオーダー)と後行順トラバーサル(ポストオーダー)という2つの走査結果が与えられたときに、元の二分木を再構築する方法をPythonで解説します。たとえば、先行順が [1,2,4,5,3,6,7]、後行順が [4,5,2,6,7,3,1] の場合、次のような二分木が得られます。一意な復元に関する注意点まず押さえておきたいのは、先行順と後行順の組み合わせだけでは、すべての内部ノードが2つの子を持つ場合に限り木が一意に決定されるという点です。子を1つしか持たないノードが存在すると、その子が左側なのか右