最近适逢秋招,于是刷了一些题。腾讯的某道题完全没有思路,看到的网上解析遂发觉了这么个经典的“模板问题”。
输入: String_1: abcdaf String_2: acbcf
输出:最长公共子序列(abcf)或长度(4)
别的方法就不进行赘述了。
首先根据两个字符串的长度m,n生成一个(m + 1)x (n + 1)的数组dp,并且令dp[m][0] = 0,dp[0][n] = 0。为什么这么做呢,这里来解释一下:
我这里借鉴了一下B站上某个外国人的做法。我们先这样做,然后依靠我们人为的去进行判断,主要目的是为了填空。动态规划是以空间换时间的利器吧,有了上个状态,经过我们的状态转移方程就可以推的下一阶段的状态。在这儿,每次操作的状态其实就是dp[i][j]的值。好,让我们进行填空。
1.如上图所示,我们看一下两个星星所标记的行和列,我们先不管已经填好的这些值,假定我们已经填好了这些,我们下一步怎么填问号的内容呢?
简单的很!自己判断嘛!子串"abc"和子串"acb"的公共子序列长度是啥啊?“ab"或者"ac"吧?我们先不管究竟是"ab"还还是"ac,先填上长度2”。
2. 就这样,我手动的全部填完了。
然后呢,我们分析一下有没有什么规律?如上图所示,我们已知结果为abcf,怎么得到的呢?或者说能不能用某个规律来说明我是怎么填好每个空的?(当然我们的大脑比较厉害,反正会填,不会的估计题都没看明白或者出题的真的是非人类了)。
结论:当两个子串逐字符进行比较的时候,若发现了相同的字符,则此刻的dp[i][j] = dp[i - 1][j - 1] + 1。除此之外,dp[i][j]就看它上面的值和左边的值哪个大了,把大的值赋给它。
注意!我们虽然写的是dp[i][j],但实际比较的是String1[i-1]和String2[j-1]。为啥呢?看图吧,当我填dp[i][j] = 1的时候,是因为我发现了String1[0] = String2[0] = "a"的时候吧?而此时的dp[i][j]中的i和j都等于1。现在多少明白为什么定义(m + 1)x(n + 1)了吧?为什么预先设定了哪些0?可以理解为两个字符串,其中一个若为空字符串,还有个锤子的公共子序列啊~
3.最后来一波总结~如上图所示,我们知道dp[i][j]**如何确定了吧!
还是很有意思的吧!下面上我的代码。
#include
#include
using namespace std;
// 声明两个字符串s1,s2
string s1, s2;
int main() {
// 输入
cin >> s1;
cin >> s2;
// 获取两个字符串的长度
int m = s1.size();
int n = s2.size();
// 声明动态规划数组
int dp[n+1][m+1];
// 填0 初始化
for(int i = 0; i < n+1; i++){dp[i][0] = 0;}
for(int i = 0; i < m+1; i++){dp[0][i] = 0;}
// 根据设定的规则一次填入dp[i][j],其中i, j >= 1
for(int i = 1; i < n+1; i++){
for(int j = 1; j < m+1; j++){
if(s1[j-1] == s2[i-1]){
dp[i][j] = dp[i-1][j-1] + 1;
}else if(dp[i][j-1] > dp[i-1][j]){
dp[i][j] = dp[i][j-1];
}else{
dp[i][j] = dp[i-1][j];
}
}
}
//打印填好的dp数组
for(int i = 1; i < n+1; i++){
for(int j = 1; j < m+1; j++){
cout << dp[i][j] << " ";
}
cout << endl;
}
// 输出最长公共子序列的长度,就是二维数组dp的最后一个元素
cout << dp[n][m] << endl;
return 0;
}
输出结果:
4.如何得到最长公共子串?
添加了一个辅助数组来完成这项工作。这里用递归的方法求解并打印。从dp数组的最后一个值开始,然后根据规则往回找,其实dp[i][j]的值来源就三个吧?要么dp[i-1][j-1],要么dp[i][j-1],要么dp[i-1][j]。所以函数的实现就比较简单了,每次递归按照具体的情况找就行了,下面是我的代码。
#include
#include
using namespace std;
string s1, s2;
// 定义二维数组用于辅助记录
int record[1000][1000];
//打印最长公共子串子串
void PrintLCS(int m, int n){
if(n == 0 || m == 0){
return;
}
if(record[n][m] == 1){
PrintLCS(m-1, n-1);
cout << s1[m-1];
}else if(record[n][m] == 2){
PrintLCS(m-1, n);
}else{
PrintLCS(m, n-1);
}
}
int main() {
cin >> s1;
cin >> s2;
int m = s1.size();
int n = s2.size();
int dp[n+1][m+1];
for(int i = 0; i < n+1; i++){dp[i][0] = 0;}
for(int i = 0; i < m+1; i++){dp[0][i] = 0;}
for(int i = 1; i < n+1; i++){
for(int j = 1; j < m+1; j++){
if(s1[j-1] == s2[i-1]){
dp[i][j] = dp[i-1][j-1] + 1;
// 若dp[i][j]的值来源于dp[i-1][j-1]+1,则给同位置的record数组赋值为1
record[i][j] = 1;
}else if(dp[i][j-1] > dp[i-1][j]){
dp[i][j] = dp[i][j-1];
//若来源于dp[i][j-1],则给同位置的record数组赋值为2
record[i][j] = 2;
}else{
dp[i][j] = dp[i-1][j];
//若来源于dp[i-1][j],则给同位置的record数组赋值为3
record[i][j] = 3;
}
}
}
for(int i = 1; i < n+1; i++){
for(int j = 1; j < m+1; j++){
cout << dp[i][j] << " ";
}
cout << endl;
}
cout << dp[n][m] << endl;
PrintLCS(m, n);
return 0;
}