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

发布时间 2023-03-30 02:05:58作者: Helica
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做法。