最长公共子序列(Longest Common Subsequence,LCS)是动态规划中的经典问题。顾名思义,即求两个序列最长的公共子序列(可以不连续)。让我们来探讨一下这个问题。
在本文中,我们规定用 s [ − 1 ] s_{[-1]} s[−1] 表示序列 s s s 的最后一个元素,用 s [ : − 1 ] s_{[:-1]} s[:−1] 表示 s s s 去掉最后一个元素后的子序列。 L C S ( s 1 , s 2 ) \mathrm{LCS}(s_1, s_2) LCS(s1,s2) 表示 s 1 s_1 s1 和 s 2 s_2 s2 的LCS的长度。
现在,假设我们有两个字符串: s 1 = "abdcbab" s_1 = \text{"abdcbab"} s1="abdcbab" 和 s 2 = "bdcbabb" s_2 = \text{"bdcbabb"} s2="bdcbabb"。我们要如何求它们的LCS呢?
状态转移:
边界条件:
综合一下,我们可以得到以下式子:
L C S ( s 1 , s 2 ) = { 0 , if s 1 or s 2 is empty L C S ( s 1 [ : − 1 ] , s 2 [ : − 1 ] ) + 1 , if s 1 [ − 1 ] = s 2 [ − 1 ] max ( L C S ( s 1 [ : − 1 ] , s 2 ) , L C S ( s 1 , s 2 [ : − 1 ] ) ) , otherwise \mathrm{LCS}(s_1, s_2) = \begin{cases} 0, & \text{if } s_1 \text{ or } s_2 \text{ is empty} \\ \mathrm{LCS}(s_{1[:-1]}, s_{2[:-1]}) + 1, & \text{if } s_{1[-1]} = s_{2[-1]} \\ \max(\mathrm{LCS}(s_{1[:-1]}, s_2), \mathrm{LCS}(s_1, s_{2[:-1]})), & \text{otherwise} \end{cases} LCS(s1,s2)=⎩ ⎨ ⎧0,LCS(s1[:−1],s2[:−1])+1,max(LCS(s1[:−1],s2),LCS(s1,s2[:−1])),if s1 or s2 is emptyif s1[−1]=s2[−1]otherwise
我们可以轻松地将其翻译成一个递归程序(我直接用Python写了):
def lcs(s1, s2):
if s1 == "" or s2 == "":
return 0
elif s1[-1] == s2[-1]:
return lcs(s1[:-1], s2[:-1]) + 1
else:
return max(lcs(s1, s2[:-1]), lcs(s1[:-1], s2))
然而,直接这样写会引起时间和空间复杂度的爆炸。我们可以使用记忆化的方法,并且传参时传索引而不是序列本身。
此外,我们还可以使用递推的方法,将时间复杂度和空间复杂度优化到 O ( n 2 ) O(n^2) O(n2)。这一算法的时间复杂度和空间复杂度均为 O ( n 2 ) O(n^2) O(n2),如果使用滚动数组,可以将空间复杂度优化到 O ( n ) O(n) O(n)。