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

Pythonで単語リストから最長の接頭辞(プレフィックス)連鎖を求める方法

この記事では、Pythonを使って「単語リストの中から、前の単語が次の単語の接頭辞(プレフィックス)になっており、次の単語にはちょうど1文字だけ新しい文字が追加されている」という条件を満たす最長の連鎖(シーケンス)の長さを求めるプログラムを紹介します。

問題の概要

小文字の文字列からなるリスト w が与えられます。この中から、以下の条件を満たす最長のシーケンスを見つけ、その長さを返します。

  • シーケンス内の各単語は、直前の単語の接頭辞である
  • 次の単語は、前の単語にちょうど1文字追加したものになっている

例えば、入力が w = ["pqr", "pq", "m", "mn", "pqrs"] の場合、出力は 3 になります。これは ["pq", "pqr", "pqrs"] というシーケンスを作れるためです。それぞれの単語は前の単語に1文字ずつ追加されており、このシーケンスの長さは3です。

解法のアプローチ

この問題は動的計画法(DP)を使うことで効率的に解けます。手順は以下の通りです。

  1. リスト w を辞書順にソートします。こうすることで、ある単語の接頭辞となる候補が必ずその単語より前に現れます。
  2. デフォルト値が0の辞書(defaultdict(int))として dp を用意します。dp[word] は「その単語で終わる最長連鎖の長さ」を表します。
  3. 結果を格納する変数 res を0で初期化します。
  4. ソート済みの各単語について、以下を処理します。
    • dp[word] = dp[word[:-1]] + 1:最後の1文字を除いた部分(=接頭辞)での連鎖の長さに1を加えます。
    • res = max(res, dp[word]):これまでの最大値を更新します。
  5. 最後に 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で最長の接頭辞連鎖を求められます。文字列操作と動的計画法を組み合わせた典型的な問題なので、ぜひ理解しておきましょう。

  1. Pythonで「a」から始まる連続増加部分文字列の最長長さを求めるプログラム

    問題の概要小文字の英字と「?」記号を含む文字列 s が与えられます。各「?」については、削除するか、任意の小文字の英字に置き換えることができます。このとき、「a」で始まる連続して増加する部分文字列(例:abcdef のようにアルファベット順に1文字ずつ進む文字列)の最長の長さを求める必要があります。例えば、入力が s = vta???defke の場合、出力は 6 になります。これは、s を vtabcdefke に変換できるためです。変換後の文字列には abcdef という連続増加部分文字列が含まれており、これが「a」で始まる最長のものとなります。解法のアプローチこの問題は、文字列を一度走査

  2. Pythonでn分木の最長パスの長さを求めるプログラムの書き方

    各要素が (u, v) という形式を持ち、u が v の親であることを表す辺リストが与えられているとします。このとき、木の中で最も長いパスの長さを求める必要があります。ここでいうパスの長さとは、「そのパスに含まれるノードの総数 + 1」のことです。 たとえば、入力が下図のような n 分木だった場合を考えてみましょう。 この場合の出力は 5 になります。なぜなら、パス [1, 4, 5, 7] には合計 4 つのノードが含まれており、パスの長さは 1 + 4 = 5 となるからです。 解き方のアプローチ この問題は、幅優先探索(BFS)を2回実行するという定番テクニックで効率よく解けます。まず