力扣-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 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步