裸题:
给两串序列,求其中最长的公共子序列的长度。
如:
A:1232193
B:482914132
最长公共子序列:232/213/293…总之长度为3。
当然也有一种变式,题意如下。
两条平行线上各有n,m个点,已知每个点的坐标,一个点只能与另一条线上的点连一条线段,两点连成的线段不能相交。问最多连多少条线段。(线段不相交问题)
如何计算?
我们发现这个问题是一个明显的动态规划。
通过求子序列的最长公共子序列来得到更长的序列的最长公共子序列。
那么如何构造转移方程?
用dp[i][j]表示第一序列到第i个,第二序列到第j个的最长子序列长度。
那么转移方程式如下:
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]要在本次用到。
在没有滚动数组的情况下,空间复杂度和时间复杂度都是O(n*m),即O(N^2)。
那么完整代码如下:
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)。
只需要把转移方程式改一下:
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