再帰で二分探索木から値を消して木をつなぎ直す

二分探索木から​指定の​値の​節を​消し、​決まりを​崩さないように​つなぎ直した​木を​返します。​子の​数で​場合を​分け、​子が​ 2 つの​ときは​右の​部分木の​いちばん​小さい​節で​置き換える​練習です。

海外で定番の問題リスト 4 本には入っていません

問題

二分探索木の根 root と、整数 key を受け取ります。値が key の節を木から消し、二分探索木の決まりを保ったまま、つなぎ直した木の根を返す関数を書いてください。

  • 二分探索木では、どの節でも、左の部分木の値はすべてその節より小さく、右の部分木の値はすべてその節より大きくなっています
  • 各節は val、left、right を持ちます。Go の節は TreeNode で、Val、Left、Right です
  • 消し方は次の通りに決めます。子が無ければ、その節をただ外します。子が 1 つだけなら、その子を節の位置へ上げます。子が 2 つなら、右の部分木でいちばん小さい値の節を、消す節の位置に置き換えます
  • key が木に無いときは、木をそのまま返します。消した結果、木が空になったら空の木 (Python は None、JavaScript は null、Go は nil) を返します
  • 節の数は 0 以上 500 以下、値はどれも違い、-100000 以上 100000 以下です

例では、木を上の段から左の順に読んだ値の並びで書きます。子が無いところは null で、並びの末尾の null は書きません。

入力と出力の例

例 1

根の 50 を消します。50 には子が 2 つあるので、右の部分木でいちばん小さい 60 が根に来ます。

入力

root = [50, 30, 70, 20, 40,
  60, 80]
key = 50

出力

[60, 30, 70, 20, 40, null, 80]

例 2

9 を消します。9 には右の子 12 だけがあるので、12 が 9 の位置へ上がります。

入力

root = [15, 9, 21, null, 12,
  18]
key = 9

出力

[15, 12, 21, null, null, 18]

例 3

節が 1 つだけの木から、その節を消します。木は空になります。

入力

root = [42]
key = 42

出力

[]

面接での聞かれ方

面接では、短く次のように渡されることが多いです。

  • 「二分探索木から、指定の値を消す関数を書いてください。返すのは新しい根です」
  • 「消す節に子が 2 つあるときは、どの節を持ってきますか?」と、置き換えの決め方を口で説明させられます
  • 書き終えたあとに「根を消すときも動きますか?」「計算量は木の形でどう変わりますか?」と聞かれます

消す節を探す所は易しく、つなぎ直す所で差がつきます。子の数で 3 つに場合を分けると先に言っておくと、聞き手も追いやすくなります。

アルゴリズムのチートシートの「再帰と木」に、​この​型の​要点を​表と​コードで​まとめています。

main.py
def delete_bst(root, key):
    # root の節は val、left、right を持ちます。空きは None です
    # ここに書きます
    pass


# 「実行」を押すと、この行の結果が出ます
# build_tree は、例と同じ並びから木を組みます
print(delete_bst(build_tree([50, 30, 70, 20, 40, 60, 80]), 50))

できているか「例で確かめる」を押すと、問題の例を全部この関数に渡して、通ったかをここに出します