LCS,LIS,LICS算法

Posted blueheart0621

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LCS,LIS,LICS算法相关的知识,希望对你有一定的参考价值。

LCS,LIS,LICS算法

首先,要理解下子串子序列的含义:

  1. 子串:来源于原序列连续的一段。
  2. 子序列:来源于原序列中元素相对顺序不变的一段,不要求元素连续。

LCS(最长公共子序列)

给定两个序列A、B,设C[i, j]=LCS(Ai, Bj),其中Ai、Bj分别表示A从首元素到第i个元素的一段、B从首元素到第j个元素的一段, ai、bi分别表示A中第i个元素、B中第j个元素。则LCS的状态转移方程为:

  1. C[i, j] = 0. ( if i == 0 or j == 0 )
  2. C[i, j] = C[i-1, j-1] + 1. ( if i,j > 0 and ai == bi )
  3. C[i, j] = max(C[i-1, j], C[i, j-1]). ( if i,j > 0 and ai != bi )

根据该递归方程可知,这是一个dp思想,实现就是构造出数组C。具体步骤如下:

  1. 先构造出第一行和第一列。
C[0, 0] ... C[0, j] ... C[0, n]
...
C[i, 0]
...
C[n, 0]
  1. 再依次构造出其余各行。
C[0, 0] ... C[0, j] ... C[0, n]
... ... ... ... ...
C[i, 0] ... C[i, j] ... C[i, n]
... ... ... ... ...
C[n, 0] ... C[n, j] ... C[n, n]

该算法复杂度为O(n^2).

LIS(最长递增子序列)

这里考虑严格递增(不严格递增类似)。给定一个序列A,设ai表示A中的第i个元素,I[i]为从首元素到以ai结尾的序列的LIS,则其状态转移方程为:

  1. I[i] = 0. ( if i == 0 )
  2. I[i] = I[i-1]. ( if ai <= ai-1)
  3. I[i] = max( I[i], I[i-1] + 1). ( if ai > ai-1 )

若只是要求求出LIS的长度,则可用一个栈来储存LIS,并结合二分来维护LIS,该栈的最后一个元素为最长LIS的尾元素,栈长即为LIS,算法复杂度为O(n*logn)。若要求输出LIS,则可以考虑求A和sort(A)的LCS,此LCS即为A的最长不减子序列,进一步得到LIS只需剔除那些多余相等的元素,算法复杂度为O(n^2)。

LCIS(最长递增公共子序列)

  • 此问题可以看成是LIS问题和LCS问题的重叠。给定两个序列A、B,设CI[i][j]为A中前i个元素,B中前j个元素且以B[j]结尾的LCIS, 则其状态转移方程为:
  1. CI[i][j] = CI[i-1][j]. ( if A[i] != B[j] )
  2. CI[i][j] = max(CI[i-1][k]) + 1. ( if A[i] == B[j] ) , 1 <= k <= j - 1 && B[k] < A[i] .

由此状态转移方程,可以写出最小O(n^2)的算法。同样用二维数组(或一维数组,具体原因参造下面“有关空间复杂度的降低”)。
如何理解上述状态转移方程:对于第一个等式,如果A[i] != B[j],而CI[i][j]是以B[j]为结尾的LCIS,则必有CI[i][j] = CI[i-1][j]。对于第二个等式,如果A[i] == B[j],则CI[i][j] 应该为CI[i-1][1] ~ CI[i-1][j-1]中LCIS最长且满足B[k] < A[i](满足递增条件),即A[i]应该接到B[1] ~ B[j-1]中小于其本身且LCIS最长的B[k]的右边。在寻找max(CI[i-1][k])的时候,其实可以在求CI[i-1][j-1]时求出,即每次计算CI[i][j]的时候,同时计算出max(CI[i][k])。这样可以将复杂度降为O(n^2)。具体实现步骤如下:

  1. 初始化max = 0,内层循环 j = 1 ~ m。
  2. if (A[i] > [B[j] && max < CI[i-1][j]),max = CI[i-1][j]。
    这样,当循环到A[i] == B[j] 时,max即为max(CI[i-1][k])。为什么是A[i] > B[j]呢,因为这个时候A[i]为考虑中的公共元素,当它大于B[j]时,说明B[j]后面可能有和它相等的数,故A[i]可能接到B[j]的后面。若是A[i] < B[j],则A[i]不可能接到B[j]的后面(因为递增子序列)。
  • 这个问题如果允许复杂度再大一点话,其实是可以转化为三个序列求LCS的问题,这三个序列分别是A,B, sort(A) (或sort(B))。若A中有重复元素,则需剔除sort(A)中的重复元素,再求三者的LCS(利用三维或者二维数组实现,可降一维原因情参造“有关空间复杂度的降低”)。可以给出具体证明:
  1. 设序列A={a0, a1, ... as},B={b0, b1, ... bt},且LCS(A, B) = {c0, c1, ... cm}, S = {s0, s1, ... sm} (其中si = LCS(ci, sort(ci))。
    则LCIS(A, B) = max(si)(其中i = 0, 1, ... m)。
  2. 因为sort(ci) 包含于sort(A), 故sort(A) = {a0‘, a1‘, ... sort(ci), ... as‘},且sort(ci)前面的元素均不大于sort(ci)中最小的元素,sort(ci)后面的元素均不小于sort(ci)中最大的元素,且|ci| = |sort(ci)|,故si = LCS(ci, sort(ci)) = LCS(ci, sort(A))。
  3. 故LCIS(A, B) = max(LCS(ci, sort(A)) = max(LCS(LCS(A, B), sort(A))) = LCS(A, B, sort(A))。

有关空间复杂度的降低

对于上面三种问题,若采用了数组来实现状态转移,且是逐行(或逐列)扫描,则可降低一维,因为上面三种问题的状态转移数组的每一位的求解时均只是利用到该位的左上及其正上和正左的元素,而且最终的答案在最后的那一行(或列)中,故可以减去一维,实现逐行(或逐列)重复扫描,从而降低了空间复杂度。

以上是关于LCS,LIS,LICS算法的主要内容,如果未能解决你的问题,请参考以下文章

LICS

最长公共上升子序列 (LIS+LCS+记录)

LIS&LCS最长上升子序列,最长公共子序列

五大常用算法:分治动态规划贪心回溯和分支界定

LCS求最长公共子序列(DP)

LIS LCS LCIS