公共子串/子序列&回文子串/子序列(C++实现)
本文主要总结做题过程中遇到的关于,子串,子序列的类似题目,对比并区分一下不同题目的相似点与不同之处。
这一类题目均用动态规划的方法实现,思路方面也差不多
需要注意两个概念问题:
子串:在字符串中连续的一部分
子序列:在字符串中可以不连续,但是字符的顺序保持不变
最长公共子串(牛客NC127)
给定两个字符串str1,str2,返回两个字符串的最长公共子串,保证最长公共子串存在且唯一
class solution {
public:
string LCS(string str1, string str2) {
int m = str1.size(), n = str2.size();
vector<vector<int>> dp(m+1, vector<int>(n+1, 0));
string res;
int maxLen = 0;
int end = 0;
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] > maxLen) {
maxLen = dp[i][j];
end = i - 1;
}
}
else {
dp[i][j] = 0;
}
}
}
res = str1.substr(end - maxLen + 1, maxLen);
return res;
}
}
dp[i][j]表示以str1第i个字符结尾,同时以str2第j个字符结尾的子串的长度,如果当前
位置字符一样,则加上两者前面一个字符结尾的子串的长度,不相等的话就等于0.
最长公共子序列(LC1143)
给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。
一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
class solution {
public:
int longestCommonSubsequence(string text1, string text2) {
int m = text1.size(), n = text2.size();
vector<vector<int>> dp(m+1, vector<int>(n+1, 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];
}
else {
dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
}
}
}
return dp[m][n];
}
}
dp[i][j]表示text1的前i个字符,与text2的前j个字符中,最大子序列的个数,由于子序列
可以不连续,所以当前遍历到的字符不相等时,状态转移方程不同于第一题中的子串。
最长回文子串(LC5)
给定一个字符串s,返回其中的最长回文子串,回文子串应该很好理解,不做解释,就是对称
的字符串,先看代码
class solution {
public:
string longestPalindrome(string s) {
if (s.size() < 2) return s;
int n = s.size();
int maxLen = 1;
int start = 0;
vector<vector<bool>> dp(n, vector<bool>(n, false));
for (int i = 0; i < n; ++i) {
dp[i][i] = true;
for (int j = 0; j < i; ++j) {
dp[j][i] = (s[i] == s[j]) && (i-j < 2 || dp[j+1][i-1]);
if (dp[j][i] && i-j+1 > maxLen) {
maxLen = i-j+1;
start = j;
}
}
}
return s.substr(start, maxLen);
}
}
dp[j][i]表示以下标j开始,到下标i结束的子串是否为回文子串,是回文子串的条件是:
s[j]与s[i]相等,并且该字串的长度小于2,如果大于2的话,那么j+1到i-1的子串必须为回文子串
以此递推
当然该题也有另一个非dp的遍历方式也可以使用,如下:
class solution {
public:
string longestPalindrome(string s) {
if (s.size() < 2) return s;
int n = s.size();
for (int i = 0; i < n; ++i) {
helper(s, i, i, maxLen, start);
helper(s, i, i+1, maxLen, start);
}
return s.substr(start, maxLen);
}
void helper(string s, int left, int right, int& maxLen, int& start) {
int n = s.size();
while (left >= 0 && right < n && s[left] == s[right]) {
--left;
++right;
}
if (right - left - 1 > maxLen) {
start = left + 1;
maxLen = right - left - 1;
}
}
}
以上方法很好理解,遍历往两边判断即可
最长回文子序列(LC516)
给定字符串s,返回其最长回文子序列的长度
class solution {
public:
int longestPalindromeSubseq(string s) {
if (s.size() < 2) return s.size();
int n = size();
vector<vector<int>> dp(n, vector<int>(n, 0));
for (int i = 0; i < n; ++i) {
dp[i][i] = 1;
for (int j = i-1; j >= 0; --j) {
if (s[i] == s[j]) {
dp[j][i] = i-j < 2 ? 2 : (dp[j+1][i-1] + 2);
}
else {
dp[j][i] = max(dp[j+1][i], dp[j][i-1]);
}
}
}
return dp[0][n-1];
}
}
其中,dp[i][j]表示下标i, j之间子串中的最大回文子序列的长度,那么返回值自然是dp[0][n-1]
转移方程看代码应该可以看懂
需要注意的是,内层的j要从靠近i的下标开始,因为当s[i]不等于s[j]时,需要用到内层的长度结
果进行比较更新,否则就会出错。
以上四题,作为总结,欢迎交流讨论,peace&love!