最長共通部分列(LCS)の問題は、指定された2つのシーケンスに同じ順序で存在する最長のサブシーケンスを見つけることです。つまり、いくつかの項目を削除することで最初の元のシーケンスから、削除することで2番目の元のシーケンスから取得できる最長のシーケンスを見つけることです。他の項目。

問題は、を見つける問題とは異なります 最長の一般的な部分文字列。部分文字列とは異なり、 サブシーケンス 元の文字列内で連続した位置を占める必要はありません。

たとえば、次の2つのシーケンスについて考えてみます。 XY:

X: ABCBDAB
Y: BDCABA
 
The length of the LCS is 4
LCS are BDAB, BCAB, and BCBA

Practice this problem

素朴な解決策は、のすべてのサブシーケンスが X[1…m] それがのサブシーケンスでもあるかどうかを確認する Y[1…n]。あるので 2m 可能なサブシーケンス X、このソリューションの時間計算量は次のようになります O(n.2m)、 どこ m 最初の文字列の長さであり、 n 2番目の文字列の長さです。

 
LCS問題には 最適な下部構造。つまり、問題はより小さく単純な「サブ問題」に分解でき、それはさらに単純なサブ問題に分解できるなど、最終的に解決策が簡単になるまで続きます。

 
1.2つのシーケンスを考えてみましょう。 XY、長さ mn 両方が同じ要素で終わること。

それらのLCSを見つけるには、最後の要素を削除して各シーケンスを短縮し、短縮されたシーケンスのLCSを見つけ、そのLCSが削除された要素を追加します。だから、私たちはそれを言うことができます。

LCS(X[1…m], Y[1…n]) = LCS(X[1…m-1], Y[1…n-1]) + X[m]    if X[m] = Y[n]

 
2.2つのシーケンスが同じシンボルで終わっていないとします。

次に、のLCS XY 2つのシーケンスのうち長い方です LCS(X[1…m-1], Y[1…n])LCS(X[1…m], Y[1…n-1])。このプロパティを理解するために、次の2つのシーケンスを考えてみましょう。

X: ABCBDAB (n elements)
Y: BDCABA (m elements)

これら2つのシーケンスのLCSは、次のいずれかで終了します。 B (シーケンスの最後の要素 X)またはしません。

ケース1: LCSがで終わる場合 B、それからそれはで終わることはできません A、削除できます A シーケンスから Y、および問題はに減少します LCS(X[1…m], Y[1…n-1]).

ケース2: LCSがで終わらない場合 B、その後削除できます B シーケンスから X そして問題はに減少します LCS(X[1…m-1], Y[1…n])。例えば、

LCS(ABCBDAB, BDCABA) = maximum (LCS(ABCBDA, BDCABA), LCS(ABCBDAB, BDCAB))
 
LCS(ABCBDA, BDCABA) = LCS(ABCBD, BDCAB) + A
LCS(ABCBDAB, BDCAB) = LCS(ABCBDA, BDCA) + B
 
LCS(ABCBD, BDCAB) = maximum (LCS(ABCB, BDCAB), LCS(ABCBD, BDCA))
LCS(ABCBDA, BDCA) = LCS(ABCBD, BDC) + A
 
And so on…

C++、Java、およびPythonの次のソリューションは、シーケンスのLCSの長さを検出します X[0…m-1]Y[0…n-1] LCS問題の最適部分構造特性を再帰的にに使用します。

C++


ダウンロード  コードを実行する

出力:

The length of the LCS is 4

Java


ダウンロード  コードを実行する

出力:

The length of the LCS is 4

Python


ダウンロード  コードを実行する

出力:

The length of the LCS is 4

上記のソリューションの最悪の場合の時間計算量は O(2(m+n)) コールスタックのスペースを占有します。 mn 文字列の長さです XY。最悪のケースは、に共通のサブシーケンスが存在しない場合に発生します XY (つまり、LCSは0です)、各再帰的呼び出しは2つの再帰的呼び出しになります。
 

 
LCS問題は 重複するサブ問題。問題の再帰的的アルゴリズムが新しいサブ問題を生成するのではなく、同じサブ問題を繰り返し解決する場合、問題には重複するサブ問題があると言われます。

LCSが0である長さ6と8の2つのシーケンスの再帰ツリーを考えてみましょう。

Longest Common subsequence – Recursion Tree

 
ご覧のとおり、同じサブ問題(同じ色で強調表示されている)が繰り返し計算されています。最適な部分構造と重複する部分問題を持つ問題は、動的プログラミングによって解決できることがわかっています。動的プログラミングでは、部分問題の解が繰り返し計算されるのではなく、メモ化されます。このメソッドは、C++、Java、およびPythonで以下に示されています。

C++


ダウンロード  コードを実行する

出力:

The length of the LCS is 4

Java


ダウンロード  コードを実行する

出力:

The length of the LCS is 4

Python


ダウンロード  コードを実行する

出力:

The length of the LCS is 4

上記のトップダウンソリューションの時間計算量は次のとおりです。 O(m.n) とが必要です O(m.n) 余分なスペース、ここで mn 文字列の長さです XY。マップの代わりにアレイを使用することもできることに注意してください。実装を確認する ここ.
 

 
上記 メモ最初に問題をサブ問題に分割し、次に値を計算して保存するため、化されたバージョンはトップダウンアプローチに従います。この問題はボトムアップで解決することもできます。ボトムアップアプローチでは、の小さい方の値を計算します LCS(i, j) まず、それらからより大きな値を構築します。

            | 0                                        if i == 0 or j == 0
LCS[i][j] = | LCS[i – 1][j – 1] + 1                    if X[i-1] == Y[j-1]
            | longest(LCS[i – 1][j], LCS[i][j – 1])    if X[i-1] != Y[j-1]

させて X なれ XMJYAUZ、 と Y なれ MZJAWXU。間の最長共通部分列 XYMJAU。次の表は、関数によって生成されます LCSLength()、のプレフィックス間のLCSの長さを示します XY。 The i'th 行と j'th 列はLCSの部分文字列の長さを示します X[0…i-1]Y[0…j-1].

Longest Common subsequence Array

 
これは、C++、Java、およびPythonで以下に示されています。

C++


ダウンロード  コードを実行する

出力:

The length of the LCS is 4

Java


ダウンロード  コードを実行する

出力:

The length of the LCS is 4

Python


ダウンロード  コードを実行する

出力:

The length of the LCS is 4

上記のボトムアップソリューションの時間計算量は次のとおりです。 O(m.n) とが必要です O(m.n) 余分なスペース、ここで mn 文字列の長さです XY。上記のソリューションのスペースの複雑さは、次のように改善できます。 O(n) LCSテーブルの行のLCSを計算するには、現在の行と前の行の解のみが必要です。

LCS問題の応用

最長共通部分列問題は、次のようなデータ比較プログラムの基礎を形成します diffユーティリティ バイオインフォマティクスの分野での使用。また、Gitなどのリビジョン管理システムでも広く使用されています。

 
こちらも参照:

kシーケンスの最長共通部分列

最長共通部分列(LCS)|スペース最適化バージョン

最長共通部分列|すべてのLCSを検索する

 
参照: https://en.wikipedia.org/wiki/Longest_common_subsequence_problem