一、题目:

二、题解
1、解题思路:
1)通过观察我们发现,题目是典型的满足线性DP性质的题目,即满足
- 最优子结构(子问题取最优,最终的大问题必然最优)
- 无后效性(即当前下标
i
的取值与其后续下标(i~n)
无关) - 子问题重叠性(解决了子问题的重叠产生的时间浪费,即空间换时间)
2)动态规划解题思路
dp[i][j]
:来表示第一个串的前i
位,第二个串的前j
位的 L C S LCS LCS的长度
1^#^重述问题:
- 从
text1(0->len_1)
,text2(0->len_2)
中选择最长公共子序列
2^#^找到最后一步:
- 从
text1
中选择了text1[i]
作为最后一位,从text2
中选了text2[j]
作为最后一位,使得公共子序列最长
3^#^去掉最后一步,问题变成了什么?
- 在
text1(0~i-1)
,text2(0~j-1)
中选择最长公共子序列。 - 原问题答案=
dp[i-1][j-1]
+(考虑i
,j
的情况)
4^#^考虑边界:
- 数组结束
2、关键代码解析:
原问题答案=dp[i-1][j-1]
+(考虑i
,j
的情况)
cpp
if(text1[1]==text2[1]) dp[1][1]=1; //对第一位数初始化
for(int i=1;i<=len1;i++)//遍历text1
{
for(int j=1;j<=len2;j++) //遍历text2
{
if(text1[i]==text2[j]) dp[i][j]=max(dp[i][j],dp[i-1][j-1]+1);
else dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
}
return dp[len1][len2]; //到[len1][len2]的最长公共子序列
1^#^. 代码的作用
这段代码的作用是:
计算两个字符串
text1
和text2
之间的最长公共子序列的长度。
什么是最长公共子序列?
最长公共子序列指的是,在不改变字符顺序的前提下,两个字符串中都出现的最长子序列 。
注意,子序列不同于子串,子序列不要求连续。
2.^#^ 代码解析
(1)初始化 dp
数组
cpp
memset(dp,0,sizeof(dp));
memset
函数用于将dp
数组初始化为0
,确保dp
里面的值不会影响后续计算。
(2)在 text1
和 text2
前面添加一个空格
cpp
text1=' '+text1;
text2=' '+text2;
- 这里在
text1
和text2
前面添加了一个空格 ,这样text1
和text2
的索引可以从1
开始,避免处理0
索引的特殊情况,提高代码可读性。
(3)获取字符串长度
cpp
int len1=text1.size()-1,len2=text2.size()-1;
- 这里计算
text1
和text2
的实际长度(去掉前面额外添加的空格)。
(4)初始化 dp[1][1]
cpp
if(text1[1]==text2[1]) dp[1][1]=1;
- 这个条件判断
text1
和text2
的第一个字符是否相同 ,如果相同,则dp[1][1] = 1
,表示最长公共子序列长度为1
。
(5)动态规划填表
cpp
for(int i=1;i<=len1;i++)// 遍历 text1
{
for(int j=1;j<=len2;j++) // 遍历 text2
{
if(text1[i]==text2[j]) dp[i][j]=max(dp[i][j],dp[i-1][j-1]+1);
else dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
}
- 外层循环 遍历
text1
的每个字符。 - 内层循环 遍历
text2
的每个字符。 - 状态转移方程 :
-
如果
text1[i] == text2[j]
:cppdp[i][j] = max(dp[i][j], dp[i-1][j-1] + 1);
- 说明当前字符匹配,最长公共子序列长度
+1
。
- 说明当前字符匹配,最长公共子序列长度
-
如果
text1[i] != text2[j]
:cppdp[i][j] = max(dp[i-1][j], dp[i][j-1]);
- 说明当前字符不匹配,我们可以选择:
- 不考虑
text1[i]
,用dp[i-1][j]
的结果 - 不考虑
text2[j]
,用dp[i][j-1]
的结果 - 取两者的
max
,得到最长公共子序列的长度。
- 不考虑
- 说明当前字符不匹配,我们可以选择:
-
(6)返回最终答案
cpp
return dp[len1][len2]; // 返回最长公共子序列的长度
dp[len1][len2]
存储了text1
的前len1
个字符和text2
的前len2
个字符的 最长公共子序列长度。
3.^#^ 复杂度分析
- 时间复杂度 :
O(n*m)
,n
和m
分别是text1
和text2
的长度。 - 空间复杂度 :
O(n*m)
,因为dp
数组占用了O(n*m)
的空间。
如果使用滚动数组优化,可以降到 O(min(n, m))
的空间复杂度。
3、完整代码解析:
cpp
const int N=1007;
int dp[N][N];
class Solution {
public:
int longestCommonSubsequence(string text1, string text2)
{
memset(dp,0,sizeof(dp));
text1=' '+text1;
text2=' '+text2;
int len1=text1.size()-1,len2=text2.size()-1;
if(text1[1]==text2[1]) dp[1][1]=1; //对第一位数初始化
for(int i=1;i<=len1;i++)//遍历text1
{
for(int j=1;j<=len2;j++) //遍历text2
{
if(text1[i]==text2[j]) dp[i][j]=max(dp[i][j],dp[i-1][j-1]+1);
else dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
}
return dp[len1][len2]; //到[len1][len2]的最长公共子序列
}
};