Pythonでk回の移動後にインデックス0へ戻る経路の数を求めるプログラム
問題概要
長さ n のリストの位置 0(インデックス 0)からスタートします。各ステップでは、「右に 1 つ進む」「左に 1 つ戻る(リストの範囲外には出られない)」「その場にとどまる」のいずれかの操作を選べます。ここで、ちょうど k ステップを使って再びインデックス 0 に戻ってくる一意な移動経路(ウォーク)の総数を求めるのがこの問題です。答えが非常に大きくなる可能性があるため、10^9 + 7 で割った余りを返します。
たとえば、入力が n = 7、k = 4 の場合、出力は 9 になります。条件を満たす操作列は次の 9 通りです。
- [右、右、左、左]
- [右、左、右、左]
- [待機、待機、待機、待機]
- [右、左、待機、待機]
- [待機、待機、右、左]
- [右、待機、待機、左]
- [右、待機、左、待機]
- [待機、右、左、待機]
- [待機、右、待機、左]
解き方のアプローチ
この問題は、再帰と動的計画法(DP)の考え方を組み合わせることで解けます。手順は以下のとおりです。
- m := 10^9 + 7(剰余を取るための定数)
- N := リストの長さ
- dp(i, jumps) という関数を定義する。i は現在位置、jumps は残りのステップ数
- jumps が 0 のとき、i が 0 なら 1、そうでなければ 0 を返す
- count := dp(i, jumps − 1)(その場にとどまるケース)
- i ≥ 0 の場合、count := count + dp(i + 1, jumps − 1)(右へ移動するケース)
- i ≤ N − 1 の場合、count := count + dp(i − 1, jumps − 1)(左へ移動するケース)
- count を返す
- メイン処理では dp(0, k) mod m を返す
それでは、実際の実装を見て理解を深めましょう。
実装例(Python)
class Solution:
def solve(self, length, n):
MOD = 10 ** 9 + 7
N = length
def dp(i, jumps):
if jumps == 0:
return +(i == 0)
count = dp(i, jumps - 1)
if i >= 0:
count += dp(i + 1, jumps - 1)
if i <= N - 1:
count += dp(i - 1, jumps - 1)
return count
return dp(0, n) % MOD
ob = Solution()
n = 7
k = 4
print(ob.solve(n, k))
入力
7, 4
出力
9
補足:計算量について
上記の実装は素朴な再帰のみで構成されているため、ステップ数 k が大きくなると計算量が指数的に増大します。functools.lru_cache などを使ってメモ化を行えば、状態数は「現在位置 × 残りステップ数」に抑えられるため、大幅な高速化が可能です。競技プログラミングなどで大きな入力を扱う場合は、メモ化の導入を検討するとよいでしょう。
-
Pythonで素数を判定するプログラムの書き方を徹底解説
はじめに この記事では、「与えられた数値が素数かどうかを判定する」という問題に対する解決策を、Pythonのコード例とともにわかりやすく解説します。 問題の概要 問題設定:ある数値が与えられたとき、その数が素数であるかどうかを判定するプログラムを作成します。 まず「素数」の定義をおさらいしましょう。1より大きい正の整数のうち、1とその数自身以外に約数を持たない数を素数(そすう)と呼びます。たとえば、2、3、5、7などはそれ以外の約数を持たないため、素数です。 プログラムの考え方 今回作成するプログラムでは、入力された数値が素数かどうかを以下の手順で判定します。 1以下の数値は素数ではない
-
Pythonでアームストロング数を判定するプログラムの書き方
この記事では、与えられた整数が「アームストロング数(Armstrong number)」であるかどうかを判定するための考え方と、Pythonによる具体的な実装方法を解説します。 問題の定義 整数 n が与えられたとき、その整数がアームストロング数であるかどうかを判定することを目標とします。 アームストロング数とは? n 桁の正の整数 abcd… が次の条件を満たすとき、この数は「n 次(オーダー n)のアームストロング数」と呼ばれます。 abcd... = a^n + b^n + c^n + d^n + … つまり、各桁の数字を「桁数乗」した値の総和が、元の数と一致するかを確認す