看过《算法导论》的人应该知道,动态规划中一个非常经典的例子就是LCS(Longest Common Length)最长公共子序列问题。下面我们来回顾一下LCS的概念。
假设有两个字符串,X=<A, B, C, B, D, A, B>,Y=<B, D, C, A, B, A>,那么它们的最长公共子序列为<B, C, B, A>,它的特点在于每个字符可以不连续。LCS问题在实际中也有非常多的应用,比如说用于论文查重等。
都说表达一个动态规划算法的精髓在于状态转移方程,那么我们就顺便回忆一下LCS的状态转移方程吧。如果用c[i, j]来表示序列Xi和Yi的LCS的长度,那么有状态转移方程:
下面进入本文的重点。如果将LCS的条件加严,要求子序列中的字符必须是连续的。那么应该如何求解这个最长公共连续子序列呢?
为了便于书写,后文中再提到“最长公共连续子序列”,我都一律用STRICT-LCS代替。
为了便于理解,还是用之前的两个字符串来举例说明什么是STRICT-LCS。X=<A, B, C, B, D, A, B>,Y=<B, D, C, A, B, A>。那么它们的最长公共连续子序列为<B, D>。
我们仍然用Dynamic Programming求解。只不过在原来的基础上需要改变。首先,我们定义c[i, j]跟原来的意义略有不同。这里c[i, j]指的是最后一个元素为Xi(=Yj)的STRICT-LCS的长度,比如X=<A, B, C>,Y=<A, B, D>那么c[3, 3]=0,不管前面如何,如果Xi和Yj不相等,就得将c[i, j]清零,作为新的开始。
LCS中,c[i, j]的值随着i、j值增大而渐渐增大,有累计效应;但是STRICT-LCS中,c[i, j]的值随时可能在某个时刻被清零。
说了这么多,把状态转移方程写出来吧:
伪代码的话,也比较简单。
1 STRICT-LCS-LENGTH(X, Y) 2 m = length[X] 3 m = length[Y] 4 for i = 1 to m 5 do c[i, 0] = 0 6 for j = 0 to n 7 do c[0, j] = 0 8 for i = 1 to m 9 do for j = 1 to n 10 do if Xi=Yj 11 then c[i, j] = c[i-1, j-1]+1 12 else c[i, j] = 0 13 return c
下面做一个显示推导过程的图。
从上图中可以找到值最大的格子2,故可知X和Y的最长公共连续子序列为BD,和AB,两个子序列的长度都为2.
e