最长公共子序列是动态规划的另一个经典问题:在给定字符串S1和S2,求一个最长的公共子串(不连续)S3,S3同时为S1和S2的子串,且要求它的长度最长,并确定这个长度。
ex:S1为abcfbc S2为abfcab的最长公共子串S3为 abcb 长度为4
分析:
1、用字符数组S1[n + 1] 和 S2[m + 1] 分别存储长度为n的字符串S1 和 长度为m的字符串S2(S1[0] 和 S2[0]不存储字符,为了方便后边化简子问题)
2、确定状态:(与之前的动态规划不同,状态数组是一个二维数组)dp[n + 1][m + 1]
3、化子问题:dp[i][j]表示以S1[i]结尾字符串和以S2[j]结尾的字符字符串的最长子序列长度(S1[0] S2[0]结尾表示空串)
ex:S1为abcfbc S2为 abfcab 则dp[2][3]表示字符串ab与字符串abf的最长子序列长度,dp[2][3] = 2
4、确定边界:dp[i][0] = 0(0 <= i <=n) dp[0][j] = 0(0 <=j <= m),即两个字符串其中一个为空串则最长子序列长度为0
6、状态转移方程,该问题需要一个两层的循环遍历1 <= i <= n,1 <= j <= m
dp[i][j]取值有两种情况:
(1)S1[i] = S2[j],即S1中的第i个字符和S2中的第j个字符相同时,此时比存在一个公共子串以S1[i] S2[j]结尾,其他部分等 价S1中前i - 1个字符和S2中前j - 1个字符的最长公共子串,于是这个子串比dp[i - 1][j - 1]多1,dp[i][j] = dp[i - 1][j - 1] + 1
(2)S1[i] != S2[j],此时最长公共子串长度为S1中前i - 1个字符和S2中前j个字符的最长公共子串长度与S1中前i个字符和S2 中前j - 1个字符的最长公共子串长度的较大者,dp[i][j] = max{dp[i - 1][j], dp[i][j - 1]}
综上状态转移方程为:
dp[i][j] = dp[i - 1][j - 1] + 1 S1[i] == S2[j]
dp[i][j] = max{dp[i - 1][j], dp[i][j - 1]} S1[i] != S2[j]
#include <iostream>
#include <stdio.h>
#include <cstring>
using namespace std;
char S1[1000];
char S2[1000];
int dp[1000][1000] = {{0, 0}}; //全部初始化为(0, 0),包含临界也初始化完毕
int main(){
//注意此处,为了方便舍弃字符串的0号位,从1号位开始输入
while(scanf("%s%s", S1 + 1, S2 + 1) != EOF){
//注意求字符串长度时也要从1号位开始
int len1 = strlen(S1 + 1);
int len2 = strlen(S2 + 1);
for(int i = 1; i <= len1; i ++){
for(int j = 1; j <= len2; j ++){
//两个转换方程
if(S1[i] == S2[j]){
dp[i][j] = dp[i - 1][j - 1] + 1;
}else{
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
cout << dp[len1][len2] << endl;
}
return 0;
}