📟作者主页:慢热的陕西人
🌴专栏链接:力扣刷题日记
📣欢迎各位大佬👍点赞🔥关注🚓收藏,🍉留言
文章目录
- 牛客热题:最长公共子序列Ⅱ
- 题目链接
- 方法一:动态规划+递归
- 思路
- 代码
- 复杂度
牛客热题:最长公共子序列Ⅱ
题目链接
最长公共子序列(二)_牛客题霸_牛客网 (nowcoder.com)
方法一:动态规划+递归
思路
整体思路:
①先求出最长子序列的长度
②根据长度去寻找对应的字符串
详细步骤:
- step 1:优先检查特殊情况。
- step 2:获取最长公共子序列的长度可以使用动态规划,我们以 d p [ i ] [ j ] dp[i][j] dp[i][j]表示在s1中以 i i i结尾,s2中以 j j j结尾的字符串的最长公共子序列长度。
- step 3:遍历两个字符串的所有位置,开始状态转移:若是 i i i位与 j j j位的字符相等,则该问题可以变成1 + d p [ i − 1 ] [ j − 1 ] dp[i - 1][j - 1] dp[i−1][j−1],即到此处为止最长公共子序列长度由前面的结果加1。
- step 4:若是不相等,说明到此处为止的子串,最后一位不可能同时属于最长公共子序列,毕竟它们都不相同,因此我们考虑换成两个子问题, d p [ i − 1 ] [ j ] dp[i - 1][j] dp[i−1][j]和 d p [ i ] [ j − 1 ] dp[i][j - 1] dp[i][j−1],我们取较大的一个就可以了,由此感觉可以用递归解决。
- step 5:但是递归的复杂度过高,重复计算了很多低层次的部分,因此可以用动态规划,从前往后加,由此形成一个表,表从位置1开始往后相加,正好符合动态规划的转移特征。
- step 6:因为最后要返回该序列,而不是长度,所以在构造表的同时要以另一个二维矩阵记录上面状态转移时选择的方向,我们用1表示来自左上方,2表示来自左边,3表示来自上边。
- step 7:获取这个序列的时候,根据从最后一位开始,根据记录的方向,不断递归往前组装字符,只有来自左上的时候才添加本级字符,因为这种情况是动态规划中两个字符相等的情况,字符相等才可用!
代码
string x = "";string ans(int i, int j, vector<vector<int>>& b){string res = "";if(i == 0 || j == 0){return res;}if(b[i][j] == 1){res += ans(i - 1, j - 1, b);res += x[i - 1];}else if(b[i][j] == 2){res += ans(i - 1, j, b);}else if(b[i][j] == 3){res += ans(i, j - 1, b);}return res;}string LCS(string s1, string s2) {//特殊情况:其中一个字符串为空if(s1.size() == 0 || s2.size() == 0){return "-1";}int len1 = s1.size();int len2 = s2.size();x = s1;//dp[i][j],表示第一个字符串第i位,第二个字符串第j位为止的最长公共子序列vector<vector<int>> dp(len1 + 1, vector<int>(len2 + 1, 0));//动态规划数组相加的方向vector<vector<int>> b(len1 + 1, vector<int>(len2 + 1, 0));//遍历两个字符串每个位置求得最长长度for(int i = 1; i <= len1; ++i)for(int j = 1; j <= len2; ++j){if(s1[i - 1] == s2[j - 1]){dp[i][j] = dp[i - 1][j - 1] + 1;//来自左上方b[i][j] = 1;}else {if(dp[i - 1][j] > dp[i][j - 1]){dp[i][j] = dp[i - 1][j];//来自左方b[i][j] = 2;}else{dp[i][j] = dp[i][j - 1];//来自右方b[i][j] = 3;}}}string res = ans(len1, len2, b);return res == "" ? "-1" : res;}
复杂度
- 时间复杂度:O(n2),构造辅助数组dp与b,两层循环,递归是有方向的递归,因此只是相当于遍历了二维数组
- 空间复杂度:O(n2),辅助二维数组dp与递归栈的空间最大为O(n2)