最长公共子序列: 两个字符串s,t中公共的,最长的,顺序严格递增的子序列。
从矩阵推出状态转化方程
例如: s = "abfcab", t = "abcfba"
a | b | f | c | a | b | |
a | 1 | 1 | ||||
b | 1 | 1 | ||||
c | 1 | |||||
f | 1 | |||||
b | 1 | 1 | ||||
a | 1 | 1 |
a | b | f | c | a | b | |
a | 1 | 0 | 0 | 0 | 1 | 0 |
b | 1 | 2 | 2 | 2 | 2 | 2 |
c | 1 | 2 | 2 | 3 | 3 | 3 |
f | 1 | 2 | 3 | 3 | 3 | 3 |
b | 1 | 2 | 3 | 3 | 3 | 4 |
a | 1 | 2 | 3 | 3 | 4 | 4 |
从这个矩阵中可以看出,最长公共子序列的长度为4, 即形成最长公共子序列的矩阵的对角线的长度。
本例中最长公共子序列有多个。
所以状态转化方程为 len[i][j] = max (len[i - 1][j], len[i][j - 1]) (s[i] != t[j])
len[i][j] = len[i - 1][j - 1] + 1; (s[i] == t[j])
len[i][j] 表示s中前i个字符和t中前j个字符的最长公共子序列的长度。
源代码
// 常规方法: 时间O(n * n), 空间O(strlen(s) * strlen (t));
#include <stdio.h>
#include <string.h>
#define MAX 500
int len[MAX + 1][MAX + 1];
int max_v (int a, int b);
int main()
{
int i, j;
char s[MAX + 1], t[MAX + 1];
while (scanf ("%s%s", s, t) == 2)
{
memset (len, 0, sizeof (len));
for (i = 0; i < strlen (t); i ++)
{
for (j = 0; j < strlen (s); j ++)
{
if (t[i] == s[j])
{
len[i + 1][j + 1] = len[i][j] + 1;
}
else
{
len[i + 1][j + 1] = max_v (len[i][j + 1], len[i + 1][j]);
}
}
}
printf ("%d\n", len[strlen (t)][strlen (s)]);
}
return 0;
}
int max_v (int a, int b)
{
if (a > b)
{
return a;
}
return b;
}
// 空间优化 1 —— 奇偶优化
// 因为len[i][j] 的值只与len[i - 1][j - 1], len[i][j - 1], len[i - 1][j]有关,所以只需要存储len[i - 1], len[i];
// 因此,空间只需要O(2 * min (strlen (s), strlen (t)));
#include <stdio.h>
#include <string.h>
#define MAX 500
int len[2][MAX + 1];
int max_v (int a, int b);
int main()
{
int i, j;
int index, last;
char s[MAX + 1], t[MAX + 1];
while (scanf ("%s%s", s, t) == 2)
{
memset (len, 0, sizeof (len));
for (i = 0; i < strlen (t); i ++)
{
index = i % 2;
last = i % 2 == 0 ? 1 : 0;
for (j = 0; j < strlen (s); j ++)
{
if (t[i] == s[j])
{
len[index][j + 1] = len[last][j] + 1;
}
else
{
len[index][j + 1] = max_v (len[last][j + 1], len[index][j]);
}
}
}
printf ("%d\n", len[index][strlen (s)]);
}
return 0;
}
int max_v (int a, int b)
{
if (a > b)
{
return a;
}
return b;
}
// 空间优化2 —— 临时变量保存len[i - 1][j - 1];
// 因此当矩阵中产生一个新行,前一行逐渐被这个新行取代。
// 前一行中为一可能用到的数据就是len[i - 1][j - 1], 用temp1保存,
// 当产生一个新的len[i]时,前一行的len[i]会被替代,所以,在更新len[i]之前需要用temp2 保存len[i],以便传给temp1;
#include <stdio.h>
#include <string.h>
#define MAX 500
int len[MAX + 1];
int max_v (int a, int b);
int main()
{
int i, j;
int temp1, temp2;
char s[MAX + 1], t[MAX + 1];
while (scanf ("%s%s", s, t) == 2)
{
memset (len, 0, sizeof (len));
for (i = 0; i < strlen (t); i ++)
{
temp1 = len[0];
for (j = 0; j < strlen (s); j ++)
{
temp2 = len[j + 1];
if (t[i] == s[j])
{
len[j + 1] = temp1 + 1;
}
else
{
len[j + 1] = max_v (len[j + 1], len[j]);
}
temp1 = temp2;
}
}
printf ("%d\n", len[strlen (s)]);
}
return 0;
}
int max_v (int a, int b)
{
if (a > b)
{
return a;
}
return b;
}