2022-10-19 17:56阅读: 90评论: 0推荐: 0

力扣-1143-最长公共子序列

稍微想一想,用双指针去扫描两个字符串就是不现实的
然后就是三步走

动态规划

dp数组定义

定义一个二维数组,dp[i][j]表示从第一个字符串i个位置为止,到第二个字符串第j个位置为止,最长公共子序列的长度

状态转移方程

第i个位置和第j个位置是否相等?

  • 如果相等
    dp[i][j] = max(dp[i-1][j],dp[i][j-1])+1
  • 如果不相等
初始化值

第一版代码是这样的

int longestCommonSubsequence(string text1, string text2) {
int m = text1.size(), n = text2.size();
// dp[i][0]和dp[0][j]都初始化为0
vector<vector<int>> dp(n + 1, vector<int>(m + 1, 0));
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
if (text2[i-1] == text1[j-1]) dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) + 1;
else dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
//for (vector<int> nums : dp) {
// for (int num : nums) cout << num << " ";
// cout << endl;
//}
return dp[n][m];
}

但是这个测试用例挂了

"bsbininm"
"jmjkbkjkv"

没有考虑到字符重复的情况
并且我意识到,对于每一个内层循环,都可以看作是一个一维动态规划问题

class Solution {
public:
int longestCommonSubsequence(string text1, string text2) {
int m = text1.size(), n = text2.size();
// dp[i][0]和dp[0][j]都初始化为0
vector<vector<int>> dp(n + 1, vector<int>(m + 1, 0));
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
if (text2[i-1] == text1[j-1]) dp[i][j] = dp[i-1][j-1] + 1;
else dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
//for (vector<int> nums : dp) {
// for (int num : nums) cout << num << " ";
// cout << endl;
//}
return dp[n][m];
}
};

这样能过,对于相等的情况,应该是同时去掉这两个字符的最长长度+1
但是对于不相等的情况,为什么是max?让我想想…
对于一行而言,i是不变的,也就是说不一致的话只需要回退j-1就行
那么为什么要回退i-1?因为j虽然跟i位置的字符不相等,但是可能跟i位置之前的字符相等

分隔

然后我们再来看看牛客这边,牛客说把它给我打印出来
但是…如果出现长度相等的不止一个结果的情况…会怎样?
好像并不是轻而易举的事

扫了一遍其他人的题解,大概是逆向遍历一遍去还原字符串

我们来看看怎么根据dp数组把这个串还原

  • 其实是相当于从右下角开始,如果左边/上面有一样数值大小的数字,说明这个位置的字符对公共子序列是没有贡献的,即不是最长公共子序列中的元素,就把它压缩掉
  • 当左边/上面的数字都不一样(小于),说明是有贡献的,就取出这个位置的字符(从任意一个字符串中取都可以,因为是公共的),逆序放到结果数组中
  • 边界条件为0时,上面两个条件判断语句会失效,直接执行最后的else

这里其实两次遍历了dp数组,也就是说,时间复杂度是O(2M*N),空间复杂度是二维数组+res一维数组的长度

int main() {
string text1, text2;
cin >> text1 >> text2;
int m = text1.size(), n = text2.size();
// dp[i][0]和dp[0][j]都初始化为0
vector<vector<int>> dp(n + 1, vector<int>(m + 1, 0));
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
if (text2[i - 1] == text1[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;
else dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
//for (vector<int> nums : dp) {
// for (int num : nums) cout << num << " ";
// cout << endl;
//}
// 还原字串
if (!dp[n][m]) cout << -1;
else {
vector<char> res(dp[n][m]);
int index = dp[n][m] - 1;
while (index >= 0) {
if (m > 0 && dp[n][m] == dp[n][m - 1]) m--;
else if (n > 0 && dp[n][m] == dp[n - 1][m]) n--;
else {
res[index--] = text2[n - 1];
n--;
m--;
}
}
for (char ch : res) cout << ch;
}
return 0;
}

留一个思考题:如果我想要打印出所有最长公共子序列呢?也就是说我想把相同长度的全部打印出来
其实逆向生成的序列应该是唯一的,也就是说dp数组也只反映了其中一种情况
那么如果我把行列倒置呢?如果有的话,我能得到另一种情况吗?

输出所有最长子序列

我在知乎看到了这张图

也就是说,其实不同的最长序列是由不同的路径决定的,而且这里的多路径代码要改成递归回溯

参考链接

本文作者:YaosGHC

本文链接:https://www.cnblogs.com/yaocy/p/16807240.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   YaosGHC  阅读(90)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起