最长公共子序列与最长公共子串区别
假设s1=“abaaca”, s2=“acaaba”。
- 最长公共子序列是s1和s2的不连续的最大公共子集。例如,s1和s2的最长公共子序列是[‘a’,’a’,’a’,’a’]
- 最长公共子串是s1和s2中连续的最长公共子串。例如,s1和s2的最长公共子串是“aca”和“aba”
最长公共子序列c++实现
两个实现差不多,这里均使用动态规划实现。实现一是最普通的动态规划,实现二则进行了状态压缩。
实现一:无状态压缩
int lcs_normal(string s1, string s2)
{
/*
dp[i][j] 表示s1前i个字符和s2前j个字符的最长公共子序列
base case: dp[0][j]=dp[i][0]=0
状态转移方程:如果s1[i-1]==s2[j-1], dp[i][j] = dp[i-1][j-1]+1;
否则dp[i][j] = max(dp[i-1][j],dp[i][j-1]);
*/
int m = s1.size();
int n = s2.size();
vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
for (int i = 1; i <= m; i++)
{
for (int j = 1; j <= n; j++)
{
if (s1[i - 1] == s2[j - 1])
{
dp[i][j] = dp[i - 1][j - 1] + 1;
}
else
{
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
return dp[m][n];
}
实现二:状态压缩
int lcs_compress(string s1, string s2)
{
/*
dp[i][j] 依赖于 dp[i-1][j-1], dp[i-1][j],dp[i][j-1]
一般状态压缩就是把行数这个信息给丢掉(因为最多依赖前一行i-1的值)
我们按行数递增的方向填表,省去行信息后,dp[j] 就等价于 dp[i-1][j]
然后由于依赖dp[i][j-1]和dp[i-1][j-1],这两个在没有行信息时都是dp[j-1]
因此这个状态压缩无论对j倒序还是顺序都不能直接解决这个问题。这里我的实现是
j进行顺序遍历,在dp[j-1]覆盖 dp[i-1][j-1]前,把dp[i-1][j-1]信息记录下来,假设为prev
也就是说,在第二层循环内,在更新dp[j]前,使用prev记录它。
需要注意的是,dp[j]本身也依赖于prev,在内循环j==1时,这个值哪来的?
因此需要在第二层循环开始前,对这个值进行初始化。prev这个值初始化为dp[i-1][0],即dp[0]
*/
int m = s1.size();
int n = s2.size();
vector<int> dp(n+1,0);
for (int i = 1; i <= m; i++)
{
int prev = dp[0];
for (int j = 1; j <= n; j++)
{
if (s1[i - 1] == s2[j - 1])
{
int tmp = dp[j];
dp[j] = prev + 1;
prev= tmp;
}
else
{ int tmp = dp[j];
dp[j] = max(dp[j], dp[j - 1]);
prev= tmp;
}
}
}
return dp[n];
}
最长公共子串c++实现
实现一:无状态压缩
int lc_substring(string s1, string s2){
/*
dp[i][j] 表示以最后一个字符为s1[i-1]和s2[j-1]的最长公共子串
base case: dp[0][j]=dp[i][0]=0
状态转移方程:如果s1[i-1]==s2[j-1], dp[i][j] = dp[i-1][j-1]+1;
否则dp[i][j] = 0;
最后并不是返回dp[m][n],而是max(dp[i][j])
*/
int m = s1.size();
int n = s2.size();
int res=0;
vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
for (int i = 1; i <= m; i++)
{
for (int j = 1; j <= n; j++)
{
if (s1[i - 1] == s2[j - 1])
{
dp[i][j] = dp[i - 1][j - 1] + 1;
}
else
{
dp[i][j] = 0;
}
res = max(res, dp[i][j]);
}
}
return res;
}
实现二:状态压缩
int lc_substring_compress(string s1, string s2){
/*
dp[i][j] 依赖于 dp[i-1][j-1],只需要前一行,因此可以直接省略行信息
对j采用倒序遍历,则dp[j-1] 等价于dp[i-1][j-1].
*/
int m = s1.size();
int n = s2.size();
int res=0;
vector<int> dp(n+1,0);
for (int i = 1; i <= m; i++)
{
for (int j = n; j >= 1; j--)
{
if (s1[i - 1] == s2[j - 1])
{
dp[j] = dp[j - 1] + 1;
}
else
{
dp[j] = 0;
}
res = max(res, dp[j]);
}
}
return res;
}