题目描述
给定两个字符串 s 1 s 2 … s n s_1 s_2…s_n s1s2…sn和 t 1 t 2 … t m t_1 t_2…t_m t1t2…tm。求这两个字符串的最长的公共子序列。字符串 s 1 s 2 … s n s_1 s_2…s_n s1s2…sn的子序列指可以表示为 s i 1 s i 2 … s i m ( i 1 < i 2 < ⋯ < i m ) s_{i_1} s_{i_2}…s_{i_m} (i_1<i_2<⋯<i_m) si1si2…sim(i1<i2<⋯<im)的序列。
解析
- 这个问题就是著名的最长公共子序列(LCS)问题,使用下面递推DP进行求解:
- dp数组的含义: d p [ i ] [ j ] = s 1 s 2 … s i dp[i][j]= s_1 s_2…s_i dp[i][j]=s1s2…si和 t 1 t 2 … t j t_1 t_2…t_j t1t2…tj的最长公共子序列的长度。
- 现在我们计算
d
p
[
i
+
1
]
[
j
+
1
]
dp[i+1][j+1]
dp[i+1][j+1],即
s
1
s
2
…
s
i
+
1
s_1 s_2…s_{i+1}
s1s2…si+1和
t
1
t
2
…
t
j
+
1
t_1 t_2…t_{j+1}
t1t2…tj+1的最长公共子序列的长度,有两种可能:
- 第一种: s ( i + 1 ) = t ( j + 1 ) s_(i+1)=t_(j+1) s(i+1)=t(j+1):那么dp[i+1][j+1]= dp[i][j]+1
- 第二种: s i + 1 ! = t j + 1 s_{i+1} != t_{j+1} si+1!=tj+1:那么dp[i+1][j+1]应该是dp[i+1][j]或者dp[i][j+1]的最大者。
- 公式总结如下: d p [ i + 1 ] [ j + 1 ] = { d p [ i ] [ j ] + 1 , s ( i + 1 ) = t ( j + 1 ) m a x ( d p [ i + 1 ] [ j ] , d p [ i ] [ j + 1 ] ) , s ( i + 1 ) ! = t ( j + 1 ) ) dp[i+1][j+1]=\begin{cases} dp[i][j]+1,& s_(i+1)=t_(j+1) \\ max(dp[i+1][j],dp[i][j+1]) ,& s_(i+1)!=t_(j+1) ) \end{cases} dp[i+1][j+1]={dp[i][j]+1,max(dp[i+1][j],dp[i][j+1]),s(i+1)=t(j+1)s(i+1)!=t(j+1))
- 由上面的公式可知,只有dp[i+1][j]和dp[i][j+1]已经计算出来就可以求出dp[i+1][j+1],即只要当前单元的上面一个单元和左边一个单元已知就可以求出当前的单元。
- 递推的初始条件: d p [ 0 − n ] [ 0 ] = d p [ 0 ] [ 0 − m ] = 0 dp[0-n][0]=dp[0][0-m]=0 dp[0−n][0]=dp[0][0−m]=0
- 组后我们的结果在dp[n][m]里面。我们只要将dp数组中的每个元素都填满就可以计算出dp[n][m],故时间复杂度为 O ( n m ) O(nm) O(nm)
代码:
#include <iostream>
using namespace std;
#define Max_n 1005
#define Max_m 1005
int n,m;
char s[Max_n],t[Max_m];
int dp[Max_n][Max_m];
//初始化
void initialize(int n,int m){
for(int i=0;i<=n;i++)
dp[i][0]=0;
for(int j=0;j<=m;j++)
dp[0][j]=0;
}
//递推求整个dp数组
void solve(){
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(s[i]==t[j]){
dp[i][j]=dp[i-1][j-1]+1;
}
else{
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
}
}
}