1143. 最长公共子序列
中等
给定两个字符串 text1
和 text2
,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0
。
一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
- 例如,
"ace"
是"abcde"
的子序列,但"aec"
不是"abcde"
的子序列。
两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。
示例 1:
输入:text1 = "abcde", text2 = "ace"
输出:3
解释:最长公共子序列是 "ace" ,它的长度为 3 。
示例 2:
输入:text1 = "abc", text2 = "abc"
输出:3
解释:最长公共子序列是 "abc" ,它的长度为 3 。
示例 3:
输入:text1 = "abc", text2 = "def"
输出:0
解释:两个字符串没有公共子序列,返回 0 。
提示:
1 <= text1.length, text2.length <= 1000
text1
和text2
仅由小写英文字符组成。
解法
- 定义 dp [ i ] [ j ] 表示 text1[0:i-1] 和 text2[0:j-1] 的最长公共子序列。 状态定义特别重要,dp[ i] [ j] 其中i和j都是开区间,所以dp[0] [0]=0只是表示两个空串的最长公共子序列,这种方式省去了所有的初始化
class Solution {
public:int longestCommonSubsequence(string text1, string text2) {// 状态定义特别重要// dp[i][j] i和j都是开区间// 如果上一个都相等,就给下一个赋值// 遍历只是表现形式int M = text1.size();int N = text2.size();// dp的初始化vector<vector<int>> dp(M + 2, vector<int>(N + 2, 0));for (int i = 1; i <= M; i++) {for (int j = 1; j <= N; j++) {if (text1[i - 1] == text2[j - 1])dp[i][j] = dp[i - 1][j - 1] + 1;elsedp[i][j] = max(dp[i][j - 1], dp[i - 1][j]);}}return dp[M][N];}
};
BM66 最长公共子串
题目链接-牛客
- 这道题我面试碰到了,当时不会做!没想到力扣没有这道题!
描述
给定两个字符串str1和str2,输出两个字符串的最长公共子串
题目保证str1和str2的最长公共子串存在且唯一。
数据范围: 1≤∣𝑠𝑡𝑟1∣,∣𝑠𝑡𝑟2∣≤50001≤∣str1∣,∣str2∣≤5000
要求: 空间复杂度 𝑂(𝑛2)O(n2),时间复杂度 𝑂(𝑛2)O(n2)
示例1
输入:
"1AB2345CD","12345EF"
复制
返回值:
"2345"
复制
备注:
1≤∣𝑠𝑡𝑟1∣,∣𝑠𝑡𝑟2∣≤5 0001≤∣str1∣,∣str2∣≤5000
解法
-
本题的区别是子序列和子串的定义不同,所以本题如果两个字符不同的话就应该直接把dp清零!
-
我自己的代码如下:
#include <vector> class Solution {public:/*** 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可** longest common substring* @param str1 string字符串 the string* @param str2 string字符串 the string* @return string字符串*/string LCS(string str1, string str2) {// write code hereint M = str1.size();int N = str2.size();int maxx = 0;vector<vector<int>>dp(M + 2, vector<int>(N + 2, 0)); //初始化、string res;for (int i = 1; i <= M; i++) {for (int j = 1; j <= N; j++) {if (str1[i - 1] == str2[j - 1]) {dp[i][j] = dp[i - 1][j - 1] + 1;if (dp[i][j] > maxx) {maxx = dp[i][j];int l = dp[i][j];res = str1.substr(i - l, l);}} else dp[i][j] = 0;}}return res;} };
后来看到别人更好的代码:
#include <iostream>
#include <vector>
#include <string>
using namespace std;string LCS(const string& str1, const string& str2) {int maxLength = 0; // 记录最长公共子串的长度int maxLastIndex = 0; // 记录最长公共子串最后一个字符在字符串str1中的位置// 创建二维动态规划表,大小为(str1.length() + 1) x (str2.length() + 1)vector<vector<int>> dp(str1.length() + 1, vector<int>(str2.length() + 1, 0));// 遍历str1和str2的每一个字符for (int i = 0; i < str1.length(); i++) {for (int j = 0; j < str2.length(); j++) {// 当字符相等时,更新dp表的值if (str1[i] == str2[j]) {dp[i + 1][j + 1] = dp[i][j] + 1;// 如果当前公共子串的长度大于之前记录的最大值,则更新最大长度和最后一个字符的位置if (dp[i + 1][j + 1] > maxLength) {maxLength = dp[i + 1][j + 1];maxLastIndex = i;}} else {// 当字符不相等时,将dp表中的值设为0,表示没有公共子串dp[i + 1][j + 1] = 0;}}}// 使用substr函数截取最长公共子串return str1.substr(maxLastIndex - maxLength + 1, maxLength);
}
这里补充一个点,求回文串、回文数等等的通法:双指针中心扩散
最长回文串
- 我先贴一份我自己写的代码,我的初始想法是,把s做一个reverse,然后求s1和s的最长公共子串,就是回文串
class Solution {
public:string longestPalindrome(string s) {// 最长回文子串=reverse的最长公共子串!string s1 = s;reverse(s.begin(), s.end());int M = s.size();vector<vector<int>> dp(M + 1, vector<int>(M + 1, 0));int maxlen = 0;int end = 0;for (int i = 1; i <= M; i++) {for (int j = 1; j <= M; j++) {if (s[i - 1] == s1[j - 1]) {dp[i][j] = dp[i - 1][j - 1] + 1;if (dp[i][j] > maxlen) {maxlen = max(maxlen, dp[i][j]); // 最大值end = i - 1;}} else {dp[i][j] = 0;}}}return s.substr(end - maxlen + 1, maxlen);}
};
- 样例都过了,卡在了后面的一个样例,说明这个方法是错的!
- 下面附正确的代码(已经写上完整注释):
class Solution {
public:// 辅助函数,用于计算以 s[l] 和 s[r] 为中心的最长回文子串的长度int help(string s, int l, int r) {int len = s.size();// 当 l 和 r 不超出范围,并且 s[l] == s[r] 时,向外扩展while (l >= 0 && r < len && s[l] == s[r]) {l--; // 左边指针左移r++; // 右边指针右移}// 返回回文子串的长度,注意此时 l 和 r 已经多走了一步return r - l - 1; // 回文串的长度为 r - l - 1}// 主函数,返回最长的回文子串string longestPalindrome(string s) {int len = s.size(); // 字符串长度int begin = 0; // 记录最长回文子串的起始位置int maxlen = 0; // 记录最长回文子串的长度// 遍历字符串中的每个字符,以它们作为回文的中心for (int i = 0; i < len; i++) {// 以 i 为中心扩展,找奇数长度的回文子串int len1 = help(s, i, i);// 以 i 和 i+1 为中心扩展,找偶数长度的回文子串int len2 = help(s, i, i + 1);// 更新最长回文子串的长度和起始位置if (len1 > maxlen || len2 > maxlen) {if (len1 > len2) {// 如果奇数长度的回文子串更长maxlen = len1; // 更新最大长度begin = i - (len1 - 1) / 2; // 计算起始位置} else {// 如果偶数长度的回文子串更长maxlen = len2; // 更新最大长度begin = i - (len2 - 2) / 2; // 计算起始位置}}}// 返回从起始位置开始的最大回文子串return s.substr(begin, maxlen);}
};