dp经典问题——最长公共子串,子序列,以及一系列类似问题

学习来自这篇文章:动态规划经典——最长公共子序列问题 (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的,只是在打印的时候费劲了点。

dp经典问题——最长公共子串,子序列,以及一系列类似问题_第1张图片

你可能感兴趣的:(面经中遇到问题)