最長共通部分列 (Longest Common Subsequence ,LCS)

概要

以下のような問題

  • 長さ\(N\)の文字列\(S\)と長さ\(M\)の文字列\(T\)がある

  • 文字列\(S\)と文字列\(T\)の共通部分列の中で、最長となる長さ、もしくは部分文字列を求めよ

以下のようなDPを考える。

  • \(dp[i][j] :=\) \(S\)のi文字目、\(T\)のj文字目からの最長部分文字列の長さ

復元は \(i = j = 0\) から始め、

  • \(S_i = T_j\) の時は解にその文字を追加し、\(i \leftarrow i + 1\), \(j \leftarrow j + 1\) に遷移

  • それ以外は \(dp[i+1][j]\), \(dp[i][j+1]\) の内、 \(dp[i][j]\) と等しくなる方の遷移

という感じで処理すればよい

計算量

\(O(NM)\)

実装

def solve(S, T):
    L1 = len(S)
    L2 = len(T)
    dp = [[0]*(L2+1) for i in range(L1+1)]

    for i in range(L1-1, -1, -1):
        for j in range(L2-1, -1, -1):
            r = max(dp[i+1][j], dp[i][j+1])
            if S[i] == T[j]:
                r = max(r, dp[i+1][j+1] + 1)
            dp[i][j] = r

    # dp[0][0] が長さの解

    # ここからは復元処理
    res = []
    i = 0; j = 0
    while i < L1 and j < L2:
        if S[i] == T[j]:
            res.append(S[i])
            i += 1; j += 1
        elif dp[i][j] == dp[i+1][j]:
            i += 1
        elif dp[i][j] == dp[i][j+1]:
            j += 1
    return "".join(res)

Verified

  • AtCoder: "Educational DP Contest - F問題: LCS": source (PyPy3, 458ms)