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

Pythonで0からnの値で形成できる一意な二分探索木の個数を求めるプログラム

ある整数 n が与えられたとき、[0, n)(0 以上 n 未満)の範囲の数値を使って生成できる一意な二分探索木(BST)の個数を求めることを考えます。答えが非常に大きくなる可能性があるため、結果は 10^9 + 7 で割った余りを返します。

たとえば、入力が n = 3 の場合、出力は 5 になります。これは {0, 1, 2} の3つの値から作れる二分探索木の形状がちょうど5通り存在するためです。

この問題の鍵となる「カタラン数」

二分探索木の個数は、キーの具体的な値には依存せず、ノードの個数 n だけで決まります。n 個のノードから構成できる二分探索木の総数は、数学では「カタラン数」として知られており、次の式で表されます。

Catalan(n) = C(2n, n) ÷ (n + 1)

ここで C(2n, n) は二項係数(2n 個の中から n 個を選ぶ組み合わせの総数)です。つまりこの問題は、カタラン数を剰余演算のもとで高速に計算する問題に帰着します。

解法のアプローチ

剰余を取りながらの除算は直接行えないため、フェルマーの小定理を利用したモジュラ逆数を使います。手順は以下のとおりです(m = 10^9 + 7 とします)。

  • 分子 numer を 1、分母 denom を n + 1 で初期化する
  • i を 1 から n までループする
    • numer ← numer × (n + i) を計算し、m で割った余りを取る
    • denom ← denom × i を計算し、m で割った余りを取る
  • ループ終了後、denom のモジュラ逆数 denom^(m−2) mod m を numer に掛ける
  • numer mod m を結果として返す

この方法なら、ループ部分は O(n)、最後のべき乗計算は O(log m) で済むため、n が大きくなっても高速に答えを求められます。

補足:なぜ denom^(m−2) で割り算ができるのか

m が素数のとき、フェルマーの小定理により a^(m−1) ≡ 1 (mod m) が成り立ちます。これを変形すると a^(m−2) ≡ a^(−1) (mod m)、つまり a の m−2 乗は「a で割る操作」と同じ働きをします。10^9 + 7 は素数なので、この性質によって除算を乗算に置き換えられます。

実装例(Python)

class Solution:
    def solve(self, n):
        m = 10 ** 9 + 7
        numer = 1
        denom = n + 1
        for i in range(1, n + 1):
            numer *= n + i
            numer %= m
            denom *= i
            denom %= m
        numer *= pow(denom, m - 2, m)
        return numer % m

ob = Solution()
print(ob.solve(4))

入力

4

出力

14

n = 4 の場合、C(8, 4) ÷ 5 = 70 ÷ 5 = 14 となり、0 から 3 までの4つの値で作れる一意な二分探索木が 14 通りであることが確認できます。

計算量の目安

  • 時間計算量:O(n + log m)
  • 空間計算量:O(1)
  1. Pythonで二分木から偶数の値を持つ葉ノードをすべて削除する方法

    二分木が与えられたとき、値が偶数であるすべての葉(リーフ)ノードを繰り返し削除する問題を考えてみましょう。削除を続けた結果、根ノードだけが残り、その値が偶数であった場合は、根ノードも併せて削除します。例えば、入力が次のような二分木だったとします。この場合、出力は次のようになります。解き方のアプローチこの問題は、後順(post-order)に近い再帰処理を使うことでシンプルに解けます。子ノードを先に処理し、その結果を受けて親ノードを判定する流れです。具体的には以下の手順に従います。関数 solve() を定義します。引数としてルートノードを受け取ります。root が null(None)の場合は

  2. 連続する「1」を含まないバイナリ文字列の数を数えるPythonプログラム

    この記事では、「連続する1が存在しないバイナリ文字列の総数を求める」という問題の解き方について、Pythonでの実装例を交えながら詳しく解説します。 問題文 問題: 正の整数 N が与えられます。このとき、長さ N のバイナリ文字列(0と1のみで構成される文字列)のうち、連続する「1」が一切含まれないものの総数を求めてください。 例えば N = 3 の場合、有効な文字列は「000」「001」「010」「100」「101」の5つとなり、「011」「110」「111」は連続する1を含むため除外されます。 アプローチ:動的計画法 この問題は動的計画法(DP)を使うことで効率的に解けます。各桁の状態を