Pythonで解く!ブラックリストの段を避けてボールが高さ0に落ちる経路の数を数える方法
問題の概要
高さ h と数値リスト blacklist が与えられます。ボールは現在 高さ h にあり、これを高さ 0 まで下ろすゲームを考えます。移動のルールは次の通りです。
- 偶数ラウンド(0から開始):ボールを 1 段・2 段・4 段のいずれかで下げられる
- 奇数ラウンド:ボールを 1 段・3 段・4 段のいずれかで下げられる
- 一部の段はブラックリストに登録されており、ボールがそこに到達すると即座に消滅する
このとき、ボールが高さ 0 に到達する移動方法の総数を求めます。答えが大きくなりすぎる場合は、109 + 7 で割った余りを返してください。
例えば、入力が h = 5、blacklist = [2, 1] の場合、出力は 2 になります。これは次の 2 通りの経路が存在するためです。
- ラウンド 0 で 1 段下がり(5 → 4)、次のラウンドで 4 → 0 へ移動する
- ラウンド 0 で 2 段下がり(5 → 3)、次のラウンドで 3 → 0 へ移動する
解き方のアプローチ
この問題は動的計画法(DP)を使うことで効率的に解けます。手順は以下の通りです。
blacklistをセット(set)に変換し、要素の存在判定を高速化します- 0 または h がブラックリストに含まれる場合は、ゴールに到達できないため 0 を返します
- サイズ h+1 の DP テーブルを作成し、各インデックスにペア [0, 0] を格納します
- 初期値として
dp[0] = [1, 1]を設定します - 剰余演算用の定数
m = 10^9 + 7を定義します - i を 1 から h まで順に処理します
- 各 x ∈ {1, 2, 3, 4} について、i − x ≥ 0 かつ i − x がブラックリストに含まれない場合
- x が 3 以外なら
dp[i][0] += dp[i−x][1](偶数ラウンドで許可される移動量による遷移) - x が 2 以外なら
dp[i][1] += dp[i−x][0](奇数ラウンドで許可される移動量による遷移)
- x が 3 以外なら
dp[i][0]とdp[i][1]を m で割った余りに更新します
- 各 x ∈ {1, 2, 3, 4} について、i − x ≥ 0 かつ i − x がブラックリストに含まれない場合
- 最終的に
dp[h][0]を返します
ここで dp[i][0] は「次の移動が偶数ラウンドになる状態」、dp[i][1] は「次の移動が奇数ラウンドになる状態」での経路数を表しています。ラウンドごとに使える移動量が異なるため、2 つの状態を分けて管理するのがポイントです。
実装例(Python)
理解を深めるために、以下の実装を見てみましょう。
def solve(h, blacklist):
blacklist = set(blacklist)
if 0 in blacklist or h in blacklist:
return 0
dp = [[0, 0] for i in range(h + 1)]
dp[0] = [1, 1]
m = 10 ** 9 + 7
for i in range(1, h + 1):
for x in [1, 2, 3, 4]:
if i - x >= 0 and i - x not in blacklist:
if x != 3:
dp[i][0] += dp[i - x][1]
if x != 2:
dp[i][1] += dp[i - x][0]
dp[i][0] %= m
dp[i][1] %= m
return dp[h][0]
h = 5
blacklist = [2, 1]
print(solve(h, blacklist))入力
5, [2, 1]
出力
2
このアルゴリズムの計算量は時間・空間ともに O(h) であり、h が大きい場合でも高速に動作します。
-
Pythonで最大k回の連続勝利となるゲーム結果の組み合わせ数を数える方法
問題の概要2つの整数 n と k が与えられます。n はこれから行う試合の総数、k は許容される連続勝利の上限です。全 n 試合の勝敗(勝ち=W、負け=L)の並びのうち、「連続した勝利が k 回以下」に収まるパターンが何通りあるかを求めます。答えは非常に大きくなる可能性があるため、109 + 7 で割った余りを返します。例えば n = 3、k = 2 の場合、答えは 7 です。条件を満たすパターンは次の 7 通りです。LLL / WLL / LWL / LLW / WWL / LWW / WLW解き方のアプローチこの問題は再帰的な動的計画法(DP)で解くことができます。i 番目の試合を処理する
-
Pythonでn個のノードから構成できる二分探索木(BST)の数を求める方法
問題の概要互いに異なるn個のノードが与えられたとき、それらを二分探索木(BST:Binary Search Tree)として配置する方法が何通りあるかを求めることを考えます。二分探索木には「左部分木には常に親より小さい値が、右部分木には常に親より大きい値が格納される」という重要な性質があります。この問題を解くには、カタラン数(Catalan Number)を利用します。カタラン数 C(n) は、n個の異なるキーから構成できる二分探索木の総数を正確に表すことが知られています。計算式は次のとおりです。$$C(n)=\frac{(2n)!}{(n+1)!\times n!}$$例えば、入力が n =