Pythonで単語リストから最長の接頭辞(プレフィックス)連鎖を求める方法
この記事では、Pythonを使って「単語リストの中から、前の単語が次の単語の接頭辞(プレフィックス)になっており、次の単語にはちょうど1文字だけ新しい文字が追加されている」という条件を満たす最長の連鎖(シーケンス)の長さを求めるプログラムを紹介します。
問題の概要
小文字の文字列からなるリスト w が与えられます。この中から、以下の条件を満たす最長のシーケンスを見つけ、その長さを返します。
- シーケンス内の各単語は、直前の単語の接頭辞である
- 次の単語は、前の単語にちょうど1文字追加したものになっている
例えば、入力が w = ["pqr", "pq", "m", "mn", "pqrs"] の場合、出力は 3 になります。これは ["pq", "pqr", "pqrs"] というシーケンスを作れるためです。それぞれの単語は前の単語に1文字ずつ追加されており、このシーケンスの長さは3です。
解法のアプローチ
この問題は動的計画法(DP)を使うことで効率的に解けます。手順は以下の通りです。
- リスト
wを辞書順にソートします。こうすることで、ある単語の接頭辞となる候補が必ずその単語より前に現れます。 - デフォルト値が0の辞書(
defaultdict(int))としてdpを用意します。dp[word]は「その単語で終わる最長連鎖の長さ」を表します。 - 結果を格納する変数
resを0で初期化します。 - ソート済みの各単語について、以下を処理します。
dp[word] = dp[word[:-1]] + 1:最後の1文字を除いた部分(=接頭辞)での連鎖の長さに1を加えます。res = max(res, dp[word]):これまでの最大値を更新します。
- 最後に
resを返します。
実装例
実際のPythonコードは以下のようになります。
from collections import defaultdict
def solve(w):
w.sort()
dp = defaultdict(int)
res = 0
for word in w:
dp[word] = dp[word[:-1]] + 1
res = max(res, dp[word])
return res
w = ["pqr", "pq", "m", "mn", "pqrs"]
print(solve(w))入力
["pqr", "pq", "m", "mn", "pqrs"]
出力
3
計算量について
単語数を n、単語の最大長を L とすると、ソートに O(n log n × L)、DPのループ処理に O(n × L) の時間がかかります。全体の計算量は O(n log n × L) となり、効率的な解法と言えます。また、辞書 dp にはすべての単語を保存するため、空間計算量は O(n × L) です。
まとめ
リストを事前にソートしておくことで、「接頭辞となる単語は必ず先に処理済み」という性質を利用でき、シンプルなDPで最長の接頭辞連鎖を求められます。文字列操作と動的計画法を組み合わせた典型的な問題なので、ぜひ理解しておきましょう。
-
Pythonで「a」から始まる連続増加部分文字列の最長長さを求めるプログラム
問題の概要小文字の英字と「?」記号を含む文字列 s が与えられます。各「?」については、削除するか、任意の小文字の英字に置き換えることができます。このとき、「a」で始まる連続して増加する部分文字列(例:abcdef のようにアルファベット順に1文字ずつ進む文字列)の最長の長さを求める必要があります。例えば、入力が s = vta???defke の場合、出力は 6 になります。これは、s を vtabcdefke に変換できるためです。変換後の文字列には abcdef という連続増加部分文字列が含まれており、これが「a」で始まる最長のものとなります。解法のアプローチこの問題は、文字列を一度走査
-
Pythonでn分木の最長パスの長さを求めるプログラムの書き方
各要素が (u, v) という形式を持ち、u が v の親であることを表す辺リストが与えられているとします。このとき、木の中で最も長いパスの長さを求める必要があります。ここでいうパスの長さとは、「そのパスに含まれるノードの総数 + 1」のことです。 たとえば、入力が下図のような n 分木だった場合を考えてみましょう。 この場合の出力は 5 になります。なぜなら、パス [1, 4, 5, 7] には合計 4 つのノードが含まれており、パスの長さは 1 + 4 = 5 となるからです。 解き方のアプローチ この問題は、幅優先探索(BFS)を2回実行するという定番テクニックで効率よく解けます。まず