看過《算法導論》的人應該知道,動態規划中一個非常經典的例子就是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