深さ優先探索で根からの道でいちばん大きい値を持ち回り、二分木のよい節を数える

根からその節までの道に、その節より大きい値が 1 つも無い節を「よい節」と呼び、その数を返します。上から下へ、ここまでの最大値を引数で渡していく深さ優先探索の練習です。

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

問題

二分木の根 root を受け取ります。根からある節までの道 (その節自身を含む) に、その節の値より大きい値が 1 つも無いとき、その節を「よい節」と呼びます。よい節の数を返す関数を書いてください。

  • 道の途中に、その節と同じ値があってもかまいません。大きい値が無ければよい節です
  • 根は、道に自分しかいないので、いつもよい節です
  • 各節は val (値)、left (左の子)、right (右の子) を持ちます。子が無いところは、Python では None、JavaScript では null、Go では nil です。Go の節は TreeNode で、Val、Left、Right と大文字で始まります
  • 節の数は 1 以上 500 以下、値はどれも -10000 以上 10000 以下です

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

入力と出力の例

例 1

山道の分かれ道ごとの標高を木にした物です。登ってきた道のどこよりも高い (または同じ高さの) 地点を数えます。根が 5、その下が 3 と 8、3 の左に 6、8 の下に 2 と 9 です。根の 5、8、9、それに左下の 6 (道は 5、3、6 で、6 より大きい値が無い) の 4 つがよい節です。

root = [5, 3, 8, 6, null, 2, 9]
4

例 2

根の 4 の左に 4、その左に 1、右に 7 があります。左の 4 は道の根と同じ値で、大きい値は無いのでよい節です。1 は、道に 4 があるのでよい節ではありません。ほかの 3 つはよい節です。

root = [4, 4, null, 1, 7]
3

例 3

節が 1 つだけの木です。根はいつもよい節です。

root = [-5]
1

面接での聞かれ方

木の深さ優先探索で「上から下へ情報を渡す」型として、ミドルの面接で出ます。

  • 「根からの道に自分より大きい値が無い節を数えてください」
  • 木の深さを求める問題のように「下から上へ値を返す」再帰と比べて、「上から下へ引数で渡す」再帰の書き方を見るために出されます
  • 「再帰を使わずに書けますか?」と聞かれることがあります。節と、そこまでの最大値の組をスタックに積めば同じことができます

ミドルの問題なので、道ごとに道の値を全部持たず、「ここまでの最大値」1 つだけを渡せばよいと気づけるかが見られます。

ヒント

まだ開いていません

付箋は 4 枚。下の段ほど答えに近づきます

  1. ヒント 2

  2. ヒント 3

  3. ヒント 4答えに近い

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

再帰は、同じ形の小さい問題に分けて自分を呼ぶ書き方です。木の深さや、全部の組み合わせを数える問題で使います。

main.py
def good_nodes(root: TreeNode | None) -> int:
    """
    Goal: 何を返すか

    idea1: まず思いつく案
    -> runtime: O(?)  space: O(?)

    idea2: もっと速い案
    -> runtime: O(?)  space: O(?)

    edge case: 空、1 つだけ、など
    """
    # root の節は val、left、right を持ちます。空きは None です
    # ここに書きます
    pass


# 「実行」を押すと、この行の結果が出ます
# build_tree は、例と同じ並びから木を組みます
print(good_nodes(build_tree([5, 3, 8, 6, None, 2, 9])))

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