求最长公共子序列长度(nlogn)【 包含了求最长上升子序列长度(nlogn)】

这里只说步骤吧,至于原理,可以自己百度找找;

假设两个序列   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;
}








猜你喜欢

转载自blog.csdn.net/godleaf/article/details/81004084