[睡前小dp] 序列相似性问题(一)LCS和LIS

  1. LCS问题

LCS:最长公共子序列,表示序列 L 和 J 最长公共的子序列长度。
计算 LCS 的经典方法是时间复杂度为 O(n*m) 的 dp。不妨设 dp[i][j] = LCS(L[0~i], J[0~j]),这样能够得到递推公式:

dp[i][j] = 0 (i=0 or j = 0)
dp[i][j] = dp[i-1][j-1] + 1 (Li == Jj)
dp[i][j] = max(dp[i-1][j], dp[i][j-1]) (Li != Jj)

这样能够在 O(n*m) 的实践完成 LCS 计算。

此外,还有一种广为流传的所谓O(nlogn)算法。这种方法首先把LCS转化为LIS问题,之后在nlogn时间内解决LIS问题。
转化方法如下:
我们在J序列中查找L序列每个元素的位置,并降序排列,记为Location,例如:

L:a c b c d e f c
J:a d e c c c f c g a
--0 1 2 3 4 5 6 6 7 9

Location_L_J(a) = [9, 0]
Location_L_J(b) = []
Location_L_J(c) = [7, 5, 4, 3]
Location_L_J(d) = [1]
Location_L_J(e) = [2]
Location_L_J(f) = [6]

再将L序列替换为Location序列,则获得L'

L': 9 0 7 5 4 3 7 5 4 3 1 2 6 7 5 4 3

我们再在L'上计算LIS,得到 [0 3 4 6 7],对应的LCS就是 a c c f c.
LIS(最长上升子序列)的O(nlogn)做法是一种空间换时间的思维。考虑序列L,定义dp[i]是LIS的值。
一个朴素的做法是,

dp[i] = max(dp[j]) + 1 (for j = 0...i and Lj < Li)

这个方法显然是O(n^2)的。

用空间换时间的方法,增加一个数组B,记录LIS为i序列的最后一个值,也就是最大值,B数组是单调递增的。这样递推公式优化为:

dp[i] = lower_bound(B[0:i-1], Li) + 1
B[i]  = L[i]

这种方法的时间复杂度计算并不是精确的,考虑到如果L和J都是单一字母组成的序列aaaaa和aaaaaaaaa,那么时间复杂度将退化到O(n*mlogn),甚至不如经典dp做法。

posted @ 2023-03-30 02:04  Helica  阅读(37)  评论(0编辑  收藏  举报