二分探索木の大小で 2 つの節の共通の祖先を探す

二分探索木の​ 2 つの​値に​ついて、​両方を​子孫に​持ついちばん深い​節 (共通の​祖先) の​値を​返します。​2 つの​値と​節の​大小だけで、​根から​ 1 本道で​下りられる​ことに​気づく​練習です。

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

問題

二分探索木の根 root と、木の中にある 2 つの値 p、q を受け取ります。値が p の節と値が q の節の両方を子孫に持つ節のうち、いちばん深い節 (共通の祖先) を探し、その節の値を返す関数を書いてください。

  • 二分探索木では、どの節でも、左の部分木の値はすべてその節より小さく、右の部分木の値はすべてその節より大きくなっています
  • 節は自分自身の子孫にも数えます。片方の節がもう片方の祖先なら、上にある方が答えです
  • 各節は val、left、right を持ちます。Go の節は TreeNode で、Val、Left、Right です
  • 節の数は 2 以上 500 以下です。値はどれも違い、-1000000000 以上 1000000000 以下です。p と q は違う値で、どちらも必ず木の中にあります。p と q のどちらが小さいかは決まっていません

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

入力と出力の例

例 1

5 と 15 は、どちらも 20 より小さいので 20 の左にあります。10 の所で左右に分かれるので、10 が答えです。

入力

root = [20, 10, 30, 5, 15, 25,
  40]
p = 5
q = 15

出力

10

例 2

3 は 2 の右、6 は 4 の右にあります。4 から見ると 3 は左、6 は右なので、4 で分かれます。

入力

root = [8, 4, 12, 2, 6, 10, 14,
  1, 3]
p = 6
q = 3

出力

4

例 3

40 は 30 の右の子です。30 自身が 40 の祖先なので、30 が答えです。

入力

root = [50, 30, 70, null, 40]
p = 30
q = 40

出力

30

面接での聞かれ方

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

  • 「二分探索木で、2 つの値の共通の祖先を返してください」
  • ふつうの二分木と同じ書き方 (両側を全部たどる) を答えると、「二分探索木であることを使えますか?」と聞き返されます
  • 「片方がもう片方の祖先のときはどうなりますか?」と、端の場合を口で確かめられます

ふつうの二分木の版と二分探索木の版は、聞かれ方が似ていて答えの速さが違います。どちらを聞かれているかを、最初に確かめておきます。

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

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


# 「実行」を押すと、この行の結果が出ます
# build_tree は、例と同じ並びから木を組みます
print(common_ancestor(build_tree([20, 10, 30, 5, 15, 25, 40]), 5, 15))

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