Pythonで最長の偶数長回文部分列の長さを求めるプログラム(動的計画法)
文字列が与えられたとき、その中から「偶数の長さを持ち、中央以外では同じ文字が2つ連続して現れない」という条件を満たす回文部分列を見つけ、その長さを出力することを考えます。
たとえば、入力が s = 'efeffe' の場合、出力は 4 になります。これは、条件を満たす偶数長の回文部分列として「effe」(長さ4)のみが存在するためです。
解法のアプローチ
この問題は、動的計画法(DP)を用いて効率的に解くことができます。手順は以下のとおりです。
nを文字列sの長さとします。dpを n × n の二次元配列として初期化します。各要素は「(長さ, 文字)」というペアで、初期値は(0, '')です。iを n-1 から 0 まで減らしながらループします。jを i+1 から n-1 まで増やしながらループします。s[i]とs[j]が同じ文字で、かつdp[i+1][j-1]に記録された文字がs[i]と異なる場合:dp[i][j]を(dp[i+1][j-1][0] + 2, s[i])に更新します。
それ以外の場合:
dp[i+1][j]、dp[i][j-1]、dp[i+1][j-1]の中から、ペアの第1要素(長さ)が最大のものを選び、dp[i][j]に代入します。
最後に
dp[0][n-1]の第1要素を返します。
アルゴリズムのポイント
このアルゴリズムの鍵は、DPテーブルに「長さ」だけでなく「内側の端の文字」も一緒に記録している点です。これにより、新しい文字ペアを追加する際に、既存の回文の端と同じ文字が隣接してしまうケース(=中央以外で同一文字が連続する状態)を確実に回避できます。
実装例
def solve(s):
n = len(s)
dp = [[(0, '')]*n for _ in range(n)]
for i in range(n-1, -1, -1):
for j in range(i+1, n):
if s[i]== s[j] and dp[i+1][j-1][1] != s[i]:
dp[i][j] = (dp[i+1][j-1][0] + 2, s[i])
else:
dp[i][j] = max(dp[i+1][j], dp[i][j-1], dp[i+1][j-1], key=lambda x: x[0])
return dp[0][n-1][0]
print(solve('efeffe'))入力
'efeffe'
出力
4
計算量
時間計算量・空間計算量はいずれも O(n²) となります。文字列の長さに比例したサイズの二次元DPテーブルを使用するためです。
-
Pythonでn分木の最長パスの長さを求めるプログラムの書き方
各要素が (u, v) という形式を持ち、u が v の親であることを表す辺リストが与えられているとします。このとき、木の中で最も長いパスの長さを求める必要があります。ここでいうパスの長さとは、「そのパスに含まれるノードの総数 + 1」のことです。 たとえば、入力が下図のような n 分木だった場合を考えてみましょう。 この場合の出力は 5 になります。なぜなら、パス [1, 4, 5, 7] には合計 4 つのノードが含まれており、パスの長さは 1 + 4 = 5 となるからです。 解き方のアプローチ この問題は、幅優先探索(BFS)を2回実行するという定番テクニックで効率よく解けます。まず
-
Pythonで二分木の指定ノードの右隣ノードを見つけるプログラム
二分木が与えられ、さらに特定のノード「u」へのポインタも渡されたとします。このとき、u のすぐ右側に位置するノード(必ず同じ階層に存在する)を見つける必要があります。対象のノードは葉ノードの場合もあれば、内部ノードの場合もあります。 例として、次のような二分木が入力されたとしましょう。 ここで u = 6 とすると、出力は 8 になります。ノード 6 の右隣にはノード 8 が存在するため、値 8 が返されるというわけです。 解決のためのアプローチ この問題は、両端キュー(deque)を使った幅優先探索(BFS)、いわゆるレベル順走査によって解くことができます。手順は以下の通りです。 ルー