最长公共子序列(LCS)

裸题:
给两串序列,求其中最长的公共子序列的长度。
如:
A:1232193
B:482914132
最长公共子序列:232/213/293…总之长度为3。数组

固然也有一种变式,题意以下。
两条平行线上各有n,m个点,已知每一个点的坐标,一个点只能与另外一条线上的点连一条线段,两点连成的线段不能相交。问最多连多少条线段。(线段不相交问题)markdown


如何计算?
咱们发现这个问题是一个明显的动态规划
经过求子序列的最长公共子序列来获得更长的序列的最长公共子序列。spa

那么如何构造转移方程
用dp[i][j]表示第一序列到第i个,第二序列到第j个的最长子序列长度。debug


那么转移方程式以下:调试

if(a[i] == b[j])    dp[i][j] = dp[i-1][j-1]+1;
else dp[i][j] = max(dp[i-1][j], dp[i][j-1]);

若是当前位置的两个数(字符)相同,那么它的”基础“不能取到dp[i][j-1]或dp[i-1][j]。由于a[i]和b[j]要在本次用到。code

在没有滚动数组的状况下,空间复杂度和时间复杂度都是O(n*m),即O(N^2)。
那么完整代码以下:string

void Dp1()
{
    for(unsigned i = 1; i != n+1; ++i)
    {
        for(unsigned j = 1; j != m+1; ++j)
        {
            if(a[i-1] == b[j-1])
            {
                dp[i][j] = dp[i-1][j-1]+1;
            }
            else
            {
                dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
            }
            debug("%d ", dp[i][j]); //调试输出
        }
        debug("\n"); //同上
    }   
    return ;
}

咱们能够发现转移方程只用到了dp[i-1][j],dp[i][j-1],dp[i-1][j-1],至于dp[i-2][j]等根本没有用到,因此咱们能够滚动数组只留下两行(一行存上一次,一行存当前)。
空间复杂度降到O(2*m),即O(N)。
只须要把转移方程式改一下:class

void Dp()
{
    for(unsigned i = 1; i != n+1; ++i)
    {
        for(unsigned j = 1; j != m+1; ++j)
        {
            if(a[i-1] == b[j-1])
            {
                dp[i&1][j] = dp[(i&1)^1][j-1]+1;
            }
            else
            {
                dp[i&1][j] = max(dp[(i&1)^1][j], dp[i&1][j-1]);
            }
            debug("%d ", dp[i][j]);
        }
        debug("\n");
    }
    return ;
}

利用运算:效率

x&1 == x%2//异
(x&1)^1 == (i-1)%2

效果相同可是效率更高基础


自此结束。

箜瑟_qi 2017.04.25 17:57

相关文章
相关标签/搜索