动态规划经典算法-最长公共子序列(Longest Common Subsequnce)

经典算法-最长公共子序列LCS

题目描述:

给定两个字符串 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 。

题目来源:leetcode1143最长公共子序列


解题思路:
  此题目要求的最长公共子序列不一定连续,所以不能使用暴力求解或者KMP。对于如此复杂的算法,要先想到切割子问题,对两个完整串的子串一步步对比,分析出它子问题的解,最终得到结果。
  以text1 = " aab", text2 = "acb"为例,我们令函数LCS(“aab”,“acb”)为最终结果。我们从后面分析,两个串的最后一个字符都为’b’,显然匹配成功,这时两个串的最长公共子序列至少为1,至于大于还是等于1那就得看剩余子串了,所以有 LCS(“aab”,“acb”) = 1+LCS(“aa”,“ac”) 这是匹配成功的情况。接下来解决LCS(“aa”,“ac”),显然最后一个字符为’a’和’c’没有匹配成功,这时要分两种情况:
  ①:让"a"和"ac"匹配,即求LCS(“a”,“ac”)。
  ②:让"aa"和”a“匹配,即求LCS(“aa”,“a”)。
  然后取两种情况的最大值。(注意:当有一个串为空时其结果为0,因为空串和任何串的最长公共子序列都为0,不用解释)

原理很简单,递归树如下:

递归树简图
  从起始的两个完整串慢慢分解到空串,在逐层返回当前的最长公共子序列长度,通过对子问题的叠加得出总体解。
  下面对两个复杂字符串进行分析,text1=“AGGTAB”,text2=“GXTXAYB”,首先列出dp数组如下图:

" "AGGTAB
" "
G
X#
T
X
A
Y
B

dp数组的解释:!所在位置表示 LCS(“AGGTAB”,“GXTXAYB”) 的结果,#所在位置表示 LCS(“AGG”,“GX”) 的结果。
首先对dp数组进行初始化,由上面分析可知,空数组与任何串的最长公共子序列都为0,即 LCS("" , 任意)=LCS(任意 ,"") = 0

初始化后的dp数组:

" "AGGTAB
" "0000000
G0
X0
T0
X0
A0
Y0
B0

  我们由开始得出的结论,假如匹配成功则结果等于1+两个串皆除去最后一个字符,反之取两个串分别除去最后一个与另一个对比的结果。
  接下来从LCS(“G”,“A”)分析,显然匹配不成功,则取LCS("",A)和LCS(“G”,"")的最大值,结果都为0,则LCS(“G”,“A”)为0,dp数组的dp[1][1]=0。到了"AG"与"G"对比时,结果匹配成功,则LCS(“AG”,“G”) = 1 + LCS(“A”,""),对于dp数组意义为dp[1][2] = 1 + dp[0][1],第二行结果如下。

" "AGGTAB
" "0000000
G0011111
X0
T0
X0
A0
Y0
B0

不难发现动态转移方程为
d p [ i ] [ j ] = { 0 , i=0 或 j=0 d p [ i − 1 ] [ j − 1 ] + 1 , text[i] = text[j] m a x ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − 1 ] ) , text[i] ≠ text[j] dp[i][j]= \begin{cases} 0,& \text{i=0 或 j=0}\\ dp[i-1][j-1]+1,& \text{text[i] = text[j]}\\ max(dp[i-1][j],dp[i][j-1]),&\text{text[i]$\neq$text[j]} \end{cases} dp[i][j]=0,dp[i1][j1]+1,max(dp[i1][j],dp[i][j1]),i=0  j=0text[i] = text[j]text[i]=text[j]
最终分析的到完整dp数组为

" "AGGTAB
" "0000000
G0011111
X0011111
T0011222
X0011222
A0111233
Y0111233
B0111234

  LCS(“AGGTAB”,“GXTXAYB”) = dp[7][6] = 4。答案正确,最长公共子序列为GTAB
算法代码如下:

class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {
	int x_len = text1.size(), y_len = text2.size();
	vector<vector<int>> dp(x_len + 1, vector<int>(y_len + 1));
	for (int i = 1; i <= x_len; ++i) {
		for (int j = 1; j <= y_len; ++j) {
			if (text1[i - 1] == text2[j - 1])  //最后一位相同则1+两个长度都-1的dp值
				dp[i][j] = 1 + dp[i - 1][j - 1];
			else
				dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
		}
	}
	return dp[x_len][y_len];
    }
};

算法时间复杂度为O(mn),空间复杂度为O(mn)。
该算法可以优化空间为O(n),有兴趣的可以自行修改。

  • 4
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值