辞書と再帰で前順と中順の並びから二分木を組み直す

二分木を​前順と​中順で​たどった​ 2 つの​並びから、​元の​木を​組み直して​返します。​前順で​根を​決め、​中順で​左右を​分ける​再帰に、​位置を​引く​辞書を​組み合わせて​ O(n) に​する​練習です。

海外で定番の問題リスト 4 本のうち 4 本に入っています (Blind 75、NeetCode 150、Grind 75、LeetCode Top Interview 150)

問題

ある二分木を 2 通りにたどった値の並び preorder と inorder を受け取ります。元の木を組み直し、その根を返す関数を書いてください。

  • preorder は前順 (自分、左の部分木、右の部分木の順) にたどった並びです
  • inorder は中順 (左の部分木、自分、右の部分木の順) にたどった並びです
  • 木の値はどれも違います。2 つの並びは同じ木から作られた正しい並びです
  • 新しい節は、Python では TreeNode(値)、JavaScript では new TreeNode(値)、Go では &TreeNode{} で作れます。どれも val (値)、left (左の子)、right (右の子) を持ち、子は最初は空です。Go では Val、Left、Right と大文字で始まります
  • 節の数は 1 以上 500 以下、値はどれも -1000 以上 1000 以下です
  • 節の数を n として、時間が O(n) で済む書き方を目指してください

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

入力と出力の例

例 1

前順の先頭の 4 が根です。中順で 4 より左の 1、2、3 が左の部分木、右の 6、7 が右の部分木になります。同じことを左右でくり返すと、次の木になります。

入力

preorder = [4, 2, 1, 3, 7, 6]
inorder = [1, 2, 3, 4, 6, 7]

出力

[4, 2, 7, 1, 3, 6]

例 2

根の -1 は中順の先頭にあるので、左の部分木は空です。右の 8 と 5 のうち、前順で先に出る 5 が右の子になり、8 はその左の子です。

入力

preorder = [-1, 5, 8]
inorder = [-1, 8, 5]

出力

[-1, null, 5, 8]

例 3

中順が前順の逆になっているので、どの節も左の子だけを持つ 1 本道の木です。

入力

preorder = [9, 6, 2]
inorder = [2, 6, 9]

出力

[9, 6, null, 2]

面接での聞かれ方

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

  • 「前順と中順の並びから、元の二分木を作ってください」
  • 最初の書き方を出すと「中順の中で根を探す所は、全体で何回の比べになりますか?」と計算量を詰められます
  • 書き終えたあとに「後順と中順なら、どこを変えますか?」「前順と後順だけでは、なぜ 1 つに決まらないのですか?」と広げて聞かれます
  • 「同じ値が入っていたら?」「節がもっと多く、再帰が深くなりすぎるなら?」と、前提を崩して聞かれることもあります

値がどれも違うことが、中順の中で根の位置を 1 つに決める前提です。最初に確かめて口に出しておきます。

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

main.py
def tree_from_orders(preorder, inorder):
    # ここに書きます
    pass


# 「実行」を押すと、この行の結果が出ます
print(tree_from_orders([4, 2, 1, 3, 7, 6], [1, 2, 3, 4, 6, 7]))

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