这里只说步骤吧,至于原理,可以自己百度找找;
假设两个序列 A,B,找两者的最长公共子序列;
第一步: 我们要去找出 A中所有与 B 相同的元素,这些元素在 B中的位置,各自为一个集合;比如 A = { a c f d } ,B = { c f g c a } , 则有 (从 i = 1 开始 )a ={ 5 } , c = { 1 ,4 } , f = { 2 }; a 这个元素对应B的第 5 个位置,c 对应B 第1 个和第 4 个位置,以此类推;
第二步: 如果元素对应B位置的集合的数字有多个,要给集合从高到低排好序,比如 c = { 4 ,1 } (排完序后)
第三步: 用这些元素的集合,替换序列 A , A 中 a这个元素换成 (5),c这个元素换成 (4,1),f这个元素换成(2),d没有对应的集合,去掉,最后得出的序列是 A = 5 4 1 2 ;
第四步: 求A = 5412 的最长上升子序列的长度,如果这里的求最长上升子序列长度的算法仍然用 复杂度为(n*n)的话,那这个求最长公共子序列长度的方法就没意义了,这里用求最长上升子序列长度(nlogn) 的方法求解,得出的长度就是最长公共子序列的长度;
上面就是全部的求解步骤;
这里也顺便把 nlogn的求最长上升子序列长度的方法说一下;
除了放要求解的序列的数组外,还需要准备另一个数组去放一条递增的序列,并时刻记录长度;
我们假设有一个数组 a ={ 1 3 4 2 7 6 8 } ,求这个数组的最长上升子序列的长度 ,另外有一个 b[ ] 的空数组,一个记录 b数组长度的变量 p = 0; 先放a 中的第一个元素 1 到b数组里面, 然后从 i = 1 开始(数组从 0 开始遍历的) 也就是 3 这个数字开始比较, 1,如果 b[ p ] < a[ i ] 满足,说明满足上升的条件,然后把 a [ i ] 这个元素加到 b[ ++p ] ,如果不相等,说明这个元素比b 这个数组中最大的那个数字要小,那么,在 b 这个数组中找出所有比 a[ i ] 小或者等于的数中最大的那个,这个数找到后用 a[ i ] 替换这个数,然后不断继续这个判断的操作; 当 a 数组遍历结束后,所得到的那个 p 也就是 b 数组的长度,就是 a 数组的最长上升子序列的长度,但是要注意的是,b 数组内的序列有可能并不是最长上升子序列,这里真正准确的是 p 的值是最长上升子序列长度的值;在找下标的时候,正好可以用 lower_bound( ) 这个函数,查找的时间复杂度是(nlogn);
下面是代码:
// 最长公共子序列 (nlogn),这里用了字符串的输入,可以同时使用字符串和数字的序列
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;
#define Maxn 100100
struct str {
char s[1];
int pos;
}B[Maxn];
char a[Maxn],b[Maxn];
int num[Maxn],tmp[Maxn];
bool cmp (const str &b1, const str &b2) {
if(b1.s[0] == b2.s[0]) return b1.pos > b2.pos;
if(b1.s[0] < b2.s[0]) return true;
else return false;
}
int lis (int *ary, int len) {
int le,ri,mid,p,tp;
tmp[0] = ary[0]; p = 0;
for (int i = 1; i < len; ++i) {
if(tmp[p] < ary[i]) tmp[++p] = ary[i];
else {
tp = lower_bound(tmp, tmp + p + 1, ary[i]) - tmp;
tmp[tp] = ary[i];
}
}
return p + 1;
}
int main(void)
{
while (cin >> a >> b) {
int len_a = strlen(a), len_b = strlen(b);
for(int i = 0; i < len_b; ++i) {
B[i].s[0] = b[i];
B[i].pos = i;
}
sort(B, B + len_b, cmp); // nlogn
int k = 0; bool ok;
for(int i = 0; i < len_a; ++i) { // 如果考虑最坏的情况的话,其实这里就已经是 (n^2)了
// 所以,找相同元素对应的位置的集合还需要去改善
ok = true; // 因为 B 里面的元素已经排好序了,如果第一次遇到相等的元素,
//之后如果遇到不相等的就可以直接退出了
for(int j = 0; j < len_b; ++j) {
if(a[i] == B[j].s[0]) { num[k++] = B[j].pos; ok = false; }
else if(!ok && a[i] != B[j].s[0]) break;
}
}
if(k == 0) cout << "0" << endl; // 如果 k == 0 说明两个序列没有相同的元素
else {
int ans = lis(num,k);
cout << ans << endl;
}
}
return 0;
}