学习来自这篇文章:动态规划经典——最长公共子序列问题 (LCS)和最长公共子串问题
可以OJ的网站:[编程题]最长公共子序列
LCS:Longest Common Substring 最长公共子串问题
文章讲解的很清楚,主要要用dp数组,dp数组中存放的是长度为i的A串和长度为B的j串,它们两者之间的最长公共子串。所以就要看A[i-1]和B[j-1]相等不?如果相等,那么dp[i][j] = dp[i-1][j-1] + 1;否则就等于0,因为这里是公共子串,必须是连续的。但是如果是子序列就不一样了,如果A[i-1]和B[j-1]不相等,那么仍然可以dp[i][j] = max(dp[i-1][j],dp[i][j-1]),因为它不要求是连续的。
一开始比较疑惑dp[i][j]为什么要去看A[i-1]和B[j-1]呢?后来想明白是因为dp[i][j]存放的是长度为i和j的子串最长匹配数目,而字符串A是从下表为0开始的。
给定两个字符串A,B,求两个字符串的最长公共子串。
例如:
A = "HelloWorld"
B = "loop"
字符串A,B最长公共子串为:lo
class LCS {
public:
int findLCS(string A, int n, string B, int m) {
if(n<=0 || m<=0 ||!A.size()||!B.size()) return 0;
vector> dp(n+1,vector(m+1,0));
int res=0;
for(int i = 1;i<=n;i++){
for(int j = 1;j<=m;j++){
if(A[i-1] == B[j-1]){
dp[i][j] = dp[i-1][j-1]+1;
res = max(res,dp[i-1][j-1]);
}
else{
dp[i][j] = 0;
}
}
}
return res;
}
};
class LCS {
public:
int findLCS(string A, int n, string B, int m) {
if(n<=0 || m<=0 ||!A.size()||!B.size()) return 0;
vector> dp(n+1,vector(m+1,0));
for(int i = 1;i<=n;i++){
for(int j = 1;j<=m;j++){
if(A[i-1] == B[j-1]){
dp[i][j] = dp[i-1][j-1]+1;
}
else{
dp[i][j] = max(dp[i][j-1],dp[i-1][j]);
}
}
}
return dp[n][m];
}
};
类似问题:
2019网易互娱面试:
给一个字符串,只能添加的情况下,判断最少添加多少个字符可以形成回文字符串。
给一个字符串,只能删除的情况下,判断最少删除多少个字符可以形成回文字符串。
解决办法就是把字符串s先逆置一下,得到s',然后将s和s'求最长公共子序列。至于为什么求的是最长公共子序列而不是子串呢?因为你可以任意的添加和删除啊,所以弄的不连续的最长公共,然后往里面添加,或者是从里面删除,得到一个回文字符串。具体做法就是直接套用dp求最长公共子序列,然后总长度n-dp[n][n]就可以了。
2019华为面试
最长回文子序列,leetcode 516. 最长回文子序列
同样的思路,逆置之后求最长公共子序列。
如果要把这个序列给输出出来呢?通过一个二维数组grid来记录每一次走的路径,因为最长公共子序列只包含三种走法,要么是A[i-1] == B[j-1],要么就是不相等,那就必须判别是从上面下来的还是从左边过来的,dp[i-1][j] 和dp [i][j-1]中的较大值。分别给三种走法标记为0,1,2.然后根据一个递归打印函数就可以了!也就是说要:记录当前状态是由之前的哪一个状态转移而来。
class Solution {
public:
int longestPalindromeSubseq(string s) {
if (!s.size()) return 0;
string t = s;
reverse(t.begin(), t.end());
int n = s.size();
int m = t.size();
vector> dp(n + 1, vector(m + 1, 0));
vector> grid(n + 1, vector(m + 1, 0));
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (s[i - 1] == t[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
grid[i][j] = 0;
}
else {
if (dp[i - 1][j] > dp[i][j - 1]) {
grid[i][j] = 1; //左边
}
else {
grid[i][j] = 2; //上边
}
}
}
}
string ret;
print(grid, n, m, s, ret);
return dp[n][m];
}
void print(vector> & grid, int i, int j,string &s,string &ret) {
if (!i || !j) return;
if (grid[i][j] == 0) {
ret.insert(ret.begin(), s[i - 1]);
print(grid, i - 1, j - 1, s, ret);
}
else if(grid[i][j] == 1) {
print(grid, i - 1, j, s, ret);
}
else {
print(grid, i, j-1, s, ret);
}
}
};
leetcode : 5. 最长回文子串
就是求最长连续子串的问题,不过在打印的时候我还没想到一个好办法,下面是遍历的一个grid地图,我现在想的是递归二维数组,找到最长的沿主对角线方向的一些坐标,就比如(1,3) -> (2,4) -> (3,5),当然肯定会保存很多,比如(1,5)这种也会保存下来,然后去求一个长度最长的,最后再得到结果。。。虽然很复杂,但是基本思路就还是LCS的,只是在打印的时候费劲了点。