最长子序列(Longest Increasing Subsequence Size)

整理自geeksforegeeks

问题描述:在一个数字随机排列的数组中找到最长的递增子序列。

解决方案

1. 动态规划(Dynamic Programming)

最优子结构:

令arr[0...n-1]是输入数组,L(i)是以arr[i]为结尾的子串中的LIS长度。那么我们可以总结出以下的规律:

if (0<j<i && arr[j]<arr[i]) L(i)=1+max(L(j))

else L(i)=1;

该解法的时间复杂度为O(n^2),我们接下来会介绍时间复杂度为O(n log n)

2. O(n long n) 解法

首先考虑一个小的sample,随后我们将解法推广到一般情况。

A={2,5,3}

观察得到两种可能的LIS:{2,3},{2,5}。

接下来我们向原来的数组中添加两个元素7,11。此时我们的LIS变成{2,3,7,11}和{2,5,7,11}。

接下来,我们再次向数组中添加一个元素8,我们这时候如何将LIS拓展呢?我们发现如果想将8加入LIS中,我们应该将11替换成8。

由于这个方法是离线的(在线算法指的是逐段处理数据,在算法开始不需要知道全部的输入序列,离线算法在必须知道所有的输入数据),我们无法得知加一个8是否会拓展LIS。假定序列中还有9,如{2,5,3,7,11,8,7,9}我们可以将11换成8,因为后面的9可以拓展LIS。

我们可以观察到,假定LIS最后一个元素是E,我们可以添加当前的元素A[i]到LIS,如果有一个元素A[j](j>i)使得E<A[i]<A[i],也可以将LIS中的一个元素替换成A[i],如果又一个元素A[j](j>i),E>A[i]<A[j]。

现在我们来考虑另一种情形,A={2,5,3},接下来的元素是1,很显然该元素并不能拓展序列{2,3}和{2,5},但是这个元素很有可能成为一个新的LIS的起点。

总地来说,我们有一个由多个active lists组成的集合,我们不断地将A[i]添加到这些lists中。我们将这些lists按照长度缩短的顺序排列并扫描,我们要找到一个末尾元素比A[i]小的序列。

我们的策略分为以下几种情况:

1. 如果A[i]比所有lists的末尾元素都小,我们便开辟一个新的活动list,其长度为1.

2. 如果A[i]比所有lists的末尾元素都大,我们便克隆一个长度最长的序列,用A[i]去拓展它。

3. A[i]处于中间位置时,我们找到一个列表末尾元素小于A[i],且该末尾元素尽可能地大,克隆该list并再用A[i]去拓展它。我们会扔掉所有长度和该新建list相同的lists。

下面有一个例子来帮助理解上面的理论:

A={0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15}

A[0] = 0. Case 1. There are no active lists, create one.
0.
-----------------------------------------------------------------------------
A[1] = 8. Case 2. Clone and extend.
0.
0, 8.
-----------------------------------------------------------------------------
A[2] = 4. Case 3. Clone, extend and discard.
0.
0, 4.
0, 8. Discarded
-----------------------------------------------------------------------------
A[3] = 12. Case 2. Clone and extend.
0.
0, 4.
0, 4, 12.
-----------------------------------------------------------------------------
A[4] = 2. Case 3. Clone, extend and discard.
0.
0, 2.
0, 4. Discarded.
0, 4, 12.
-----------------------------------------------------------------------------
A[5] = 10. Case 3. Clone, extend and discard.
0.
0, 2.
0, 2, 10.
0, 4, 12. Discarded.
-----------------------------------------------------------------------------
A[6] = 6. Case 3. Clone, extend and discard.
0.
0, 2.
0, 2, 6.
0, 2, 10. Discarded.
-----------------------------------------------------------------------------
A[7] = 14. Case 2. Clone and extend.
0.
0, 2.
0, 2, 6.
0, 2, 6, 14.
-----------------------------------------------------------------------------
A[8] = 1. Case 3. Clone, extend and discard.
0.
0, 1.
0, 2. Discarded.
0, 2, 6.
0, 2, 6, 14.
-----------------------------------------------------------------------------
A[9] = 9. Case 3. Clone, extend and discard.
0.
0, 1.
0, 2, 6.
0, 2, 6, 9.
0, 2, 6, 14. Discarded.
-----------------------------------------------------------------------------
A[10] = 5. Case 3. Clone, extend and discard.
0.
0, 1.
0, 1, 5.
0, 2, 6. Discarded.
0, 2, 6, 9.
-----------------------------------------------------------------------------
A[11] = 13. Case 2. Clone and extend.
0.
0, 1.
0, 1, 5.
0, 2, 6, 9.
0, 2, 6, 9, 13.
-----------------------------------------------------------------------------
A[12] = 3. Case 3. Clone, extend and discard.
0.
0, 1.
0, 1, 3.
0, 1, 5. Discarded.
0, 2, 6, 9.
0, 2, 6, 9, 13.
-----------------------------------------------------------------------------
A[13] = 11. Case 3. Clone, extend and discard.
0.
0, 1.
0, 1, 3.
0, 2, 6, 9.
0, 2, 6, 9, 11.
0, 2, 6, 9, 13. Discarded.
-----------------------------------------------------------------------------
A[14] = 7. Case 3. Clone, extend and discard.
0.
0, 1.
0, 1, 3.
0, 1, 3, 7.
0, 2, 6, 9. Discarded.
0, 2, 6, 9, 11.
----------------------------------------------------------------------------
A[15] = 15. Case 2. Clone and extend.
0.
0, 1.
0, 1, 3.
0, 1, 3, 7.
0, 2, 6, 9, 11.
0, 2, 6, 9, 11, 15. <-- LIS List
----------------------------------------------------------------------------

如果我们只是想知道最长序列的长度,那我们就只需要处理各个序列的末尾元素,将所有末尾的元素存到一个数组中,这样能节省空间。

猜你喜欢

转载自blog.csdn.net/weixin_30104533/article/details/81011945