动态规划(2)-最长增序列

来源:互联网 发布:苹果手机4g网络不稳定 编辑:程序博客网 时间:2024/05/02 08:53

最长增序列(Longest Increasing Subsequence,LIS )给定一个数列,从中删掉任意若干项剩余的序列叫做它的一个子序列,求它的最长的子序列,满足子序列中的元素是单调递增的。
例如给定序列{1,6,3,5,4},答案是3,因为{1,3,4}和{1,3,5}就是长度最长的两个单增子序列。

暴力枚举

C(n,0)+C(n,1)+…+C(n,n)=2n,还不算对单个C(n,i)比较。
其实只需要考虑加入的数比之前加入的最后一个数大就可以了。而最长的意思是数的个数最多,我们只要知道数的总个数就可以了,没必要知道具体有哪些数。

动态规划

假设这个序列是a1,a2,a3an,为了方便我们加入a0=,很显然序列为空LIS的长度就是0嘛。f[i]代表以第i个数结尾的最长单调子序列的长度。假如LIS加入ai之前的最后一个数是aj,显然j< i 且aj<ai的好处很大,省去判断原序列为空的情景。

以下记录算法过程加深对算法理解:

j=0,f[1]=1j=0,f[2]=1j=1,f[2]=2j=0,f[3]=1j=1,f[3]=1j=2,f[3]=1j=0,f[4]=1j=1,f[4]=1j=2,f[4]=1j=3,f[4]=2j=0,f[5]=1j=1,f[5]=1j=2,f[5]=1j=3,f[5]=2j=4,f[5]=3j=0,f[6]=1j=1,f[6]=2j=2,f[6]=3j=3,f[6]=3j=4,f[6]=3j=5,f[6]=4j=0,f[7]=1j=1,f[7]=2j=2,f[7]=3j=3,f[7]=3j=4,f[7]=3j=5,f[7]=4j=6,f[7]=4j=0,f[8]=1j=1,f[8]=2j=2,f[8]=3j=3,f[8]=3j=4,f[8]=3j=5,f[8]=4j=6,f[8]=4j=7,f[8]=5

回溯过程如下图:
2.1

先从f中找到最大的那个i,其在A中对应的元素就是LIS的最后一个,然后一项一项不断向f[:i]中找最大的即可。

为了方便代码只找一个LIS(所有最大递增子序列中,最大值下标最小,由index函数决定)实际上可以找多个LIS,那就是最大的那个i有多个的情况。

时间复杂度O(n2),空间复杂度O(n),嫌时间复杂度大?事实上这个题有时间复杂度更低的算法。

以{1,6,3,5,4}为例子,我们想像考虑5的时候,之前有两个长度为2的子序列{1,6}和{1,3},那么哪个更“好”呢?显然后者更好,因为3比6小,以3结尾的序列更容易在后面接上一个数。也就是说一个序列,长度为n的递增子序列可能不止一个,但是所有长度为n的子序列中,有一个子序列是比较特殊的,那就是最大元素最小的递增子序列。这就类似贪心策略,那么问题明了了,开始我们只有一个长度为0的单调子序列,末尾大小认为是-∞,这个序列可以用栈来存储(对python来说list就是栈)。对于A里面的元素ai,如果比栈顶元素大,那就进栈,如果比他小,那我们就从后往前用它替换第一次比它小的元素(比较拗口,和插入排序插入过程一样,只不过这个是替换),我们替换过去,至少不会变差,这也正式我们保存每个长度“最好”的单调子序列的初衷。那么插入排序有个二分插入法,可以让插入的复杂度变成logn,仿照这个做替换,总的时间复杂度也就降低为O(nlogn)
最后LIS的长度就是栈长度。
整个算法过程如图所示,这里省略了最左边的-INF:

2.2

那如何找到具体一个子序列呢?

观察图可以发现,
[ 3]
[ 3, 4]
[1, 2, 3]
[ 1, 2, 3, 7]
[1, 2, 3, 6, 7]
均是stack入栈后的序列,只要打印最后一个入栈后的队列即可得到这个序列的LIS,当然打印LIS还有其他办法,大家可以分享。
对应代码:

#LIS O(nlogn)import sysA=[3,4,1,2,3,7,6,7]n=len(A)stack=[-sys.maxsize]for i in range(n):    if A[i]>stack[-1]:#如果比栈顶元素大那么加入栈        stack.append(A[i])        res=stack.copy()    else:#二分检索栈中比A[i]大的第一个数        low=0;high=len(stack)-1        while low<=high:            mid=(low+high)//2            if A[i]>stack[mid]:                low=mid+1            else:                high=mid-1        stack[low]=A[i]print (res[1:])#打印LISprint(len(stack)-1)#打印LIS长度
1 0