最长公共子序列——动态规划
本题的最优子结构比较难找,书上直接给出了定理,定理给出三种情况
- 两个串最后一个字母相等,那么最长序列等于两个串都除去最后一个字母的最长序列+1
- 最后一个字母不相等,最长序列是两个串各除去最后一个字母后的最长序列的较大者。
这样原问题的最优解就包含了子问题的最优解(除去最后一个字母的串的求解),符合最优子结构。
根据子结构写出递归式,用二维数组记录解空间,然后根据求c[i][j]时需要依赖的表项构造顺序求解。(依赖于左,左上,上的表项)
LCS(x,y) =
(1) LCS(x - 1,y - 1) + 1 如果Ax = By
(2) max(LCS(x – 1, y) , LCS(x, y – 1)) 如果Ax ≠ By
(3) 0 如果x = 0或者y = 0
解空间:
int c[100][100];
int lcs_length(const char *strA, const char *strB)
{
int lenA = strlen(strA);
int lenB = strlen(strB);
for (int i = 0; i <= lenA; i++) {
c[0][i] = 0;
}
for (int i = 0; i <= lenB; i++) {
c[i][0] = 0;
}
//求c[i][j]根据递归式,依赖于表格左,左上,上,所以求解顺序为行主序
for (int i = 1; i <= lenA; i++) {
for (int j = 1; j <= lenB; j++) {
//上面下标都和表格对齐,字符串由于从0下标开始
if (strA[i-1] == strB[j-1]) {
c[i][j] = c[i - 1][j - 1] + 1;
}
else if (c[i][j - 1] >= c[i - 1][j]) {
c[i][j] = c[i][j - 1];
}
else {
c[i][j] = c[i-1][j];
}
}
}
return c[lenA][lenB];
}
构造最优解
在如上图的解空间里,每一行都是当前规模问题的最优解,用箭头记录了当前规模最优解的来路。
所以再顺着箭头递归找回去就可以了。
int c[100][100];
int s[100][100];
int lcs_length(const char *strA, const char *strB)
{
int lenA = strlen(strA);
int lenB = strlen(strB);
for (int i = 0; i <= lenA; i++) {
c[0][i] = 0;
}
for (int i = 0; i <= lenB; i++) {
c[i][0] = 0;
}
//求c[i][j]根据递归式,依赖于表格左,左上,上,所以求解顺序为行主序
for (int i = 1; i <= lenA; i++) {
for (int j = 1; j <= lenB; j++) {
if (strA[i-1] == strB[j-1]) {
c[i][j] = c[i - 1][j - 1] + 1;
s[i][j] = 'Q'; //Q代表斜,L左,U上
}
else if (c[i][j - 1] > c[i - 1][j]) {
c[i][j] = c[i][j - 1];
s[i][j] = 'L';
}
else {
c[i][j] = c[i-1][j];
s[i][j] = 'U';
}
}
}
for (int i = 1; i <= lenA; i++) {
for (int j = 1; j <= lenB; j++) {
printf("%c ", s[i][j]);
}
printf("\n");
}
return c[lenA][lenB];
}
void print_lcs(int lenA, int lenB, const char *strA)
{
if (0 == lenA || 0 == lenB) {
return;
}
if (s[lenA][lenB] == 'Q') {
print_lcs(lenA - 1, lenB - 1, strA);
printf("%c ", strA[lenA-1]);
}
else if (s[lenA][lenB] == 'L') {
print_lcs(lenA, lenB - 1, strA);
}
else {
print_lcs(lenA-1, lenB, strA);
}
}