最長公共子序列
- 本文講解的題與leetcode1143.最長公共子序列這題一樣,閱讀完可以挑戰一下。
力扣題目連結
題目敘述:
給定兩個字串,輸出其最長公共子序列,並輸出它的長度
輸入:
ADABEC和DBDCA
輸出:
DBC
3
解釋
最長公共子序列是DBC,其長度為3
動態規劃思路:
- 我們這題先構建一個模型,我們使用兩個指標
i
,j
,分別用於遍歷a字串,b字串。如圖所示:
-
然後我們可以設想一個狀態變數,也就是一個函式。一個關於兩個變數相關的函式,這在程式碼中體現為二維陣列
f
。 -
然後
f[i][j]
表示什麼呢?表示序列a[1,2,3....i]
和b[1,2,3....j]
的最長公共子序列的長度
狀態變數的含義
-
在這裡的狀態變數為
f[i][j]
,它的含義是a的前i個字元與b的前j個字元的最長公共子序列的長度 -
現在就要觀察
a[i]
,b[j]
是否在當前的最長公共子序列當中。 -
具體情況如下圖:
遞推公式:
f[i][j]
可以分為三種情況討論,就是:
a[i]
,b[j]
都在最長公共子序列當中,也就是a[i]==b[j]
a[i]!=b[j]
,並且a[i]
不在公共子序列當中。a[i]!=b[j]
,並且b[j]
不在公共子序列當中。
- 那我們的遞推公式就可與分為兩種情況:
f[i][j]=f[i-1][j-1]+1
(a[i]==b[j]
)f[i][j]=max(f[i-1][j],f[i][j-1])
(a[i]!=b[j]
)
- 顯而易見,我們的邊界條件為:
f[0][j]=0
f[i][0]=0
//m是a字串的長度,n是b字串的長度
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
//因為我們的f陣列是從下標1開始,而字串是從0開始的下標
if(a[i-1]==b[j-1]) f[i][j]=f[i-1][j-1]+1;
else f[i][j]=max(f[i-1][j],f[i][j-1]);
}
}
遍歷順序
- 經過上面的分析,明顯遍歷順序為
i從小到大
,j也是從小到大
。
初始化
- 初始化邊界為0即可
舉例列印dp陣列
- 如圖所示
如何找出對應的最長公共子序列的長度
-
我們使用p陣列來記錄每一次
f[i][j]
的值來源於哪一個方向- 1方向代表左上方
- 2方向代表左方
- 3方向代表上方
-
程式碼改造如下:
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
if(a[i-1]==b[j-1]){
f[i][j]=f[i-1][j-1]+1;
//左上方
p[i][j]=1;
}
else if(f[i-1][j]>f[i][j-1]){
f[i][j]=f[i][j-1];
//左邊
p[i][j]=2;
}
else{
f[i][j]=f[i-1][j];
//上邊
p[i][j]=3;
}
}
}
p[i][j]
代表前驅的位置。
演算法的執行過程
- 我們要找到最長公共子序列,只需要找到從結尾開始,往前找到
p[i][j]==1
,也就是來源於左上方的哪些元素的集合,就是我們的最長公共子序列。(並不是棋盤中所有p[i][j]==1
)的元素,而是從右下角出發,往回找到的所有p[i][j]==1
的那些元素。 - 例子如下:
-
我們使用
s陣列
來儲存最長公共子序列 -
程式碼實現:
int i,j,k;
char s[200];
i=m;j=n;k=f[m][n];
while(i>0&&j>0){
//左上方
if(p[i][j]==1){
s[k--]=a[i-1];
i--;j--;
}
//左邊
else if(p[i][j]==2) j--;
//上邊
else i--;
}
for(int i=1;i<=f[m][n];i++) cout<<s[i];
最終程式碼實現:
#include <iostream>
#include <cstring>
using namespace std;
char a[200];
char b[200];
int f[205][205];
int p[205][205];
int m, n;
void LCS() {
int i, j;
m = strlen(a);
n = strlen(b);
for (i = 1; i <= m; i++) {
for (j = 1; j <= n; j++) {
if (a[i - 1] == b[j - 1]) {
f[i][j] = f[i - 1][j - 1] + 1;
p[i][j] = 1;
}
else if (f[i - 1][j] > f[i][j - 1]) {
f[i][j] = f[i - 1][j];
p[i][j] = 2;
}
else {
f[i][j] = f[i][j - 1];
p[i][j] = 3;
}
}
}
cout << f[m][n] << endl;
}
//尋找出當初的最長公共子序列。
void getLCS() {
int i = m, j = n, k = f[m][n];
char s[200];
s[k] = '\0';
while (i > 0 && j > 0) {
if (p[i][j] == 1) {
s[--k] = a[i - 1];
i--; j--;
}
else if (p[i][j] == 2) {
i--;
}
else {
j--;
}
}
cout << s << endl;
}
int main() {
cin >> a >> b;
LCS();
getLCS();
return 0;
}