最长递增子序列longest increasing subsequence
给定数组arr,返回arr的最长递增子序列。
arr=[2,1,5,3,6,4,8,9,7],返回的最长递增子序列为[1,3,4,8,9]。
如果arr长度为N,请实现时间复杂度为O(NlogN)的方法。
*子序列不一定连续,子串和子数组一定连续
思路 & 代码
时间复杂度为O(N^2)
产生dp数组的过程(长度就是arr的长度)
dp[i]=Max{dp[j]+1(0<=j<i, arr[j]<arr[i])}
- dp[i]表示以i结尾的数组的最长子序列的长度
- dp[0] = 1
- 找到大于当前值i的位置j,dp[i] = dp[j]+1
- 找不到大于当前值i的位置j,dp[i] = 1
找到LIS,找到dp的情况下
- dp中的最大值就是子序列的长度
- dp中最大值对应的arr就是子序列中的一个
- 倒叙找到出比前面值arr[i]小的并且dp[i]=dp[j]+1
|
|
时间复杂度O(N*logN)
从上面的计算dp过程可以看出,重复访问以前计算的dp值,会降低效率
因此添加一个ends数组,二分计算
ends[i]表示当前计算状态下,长度为i+1的LIS的最后结尾字符的最小值,计算ends的过程是与计算dp的过程同步的,
right是ends中的有效范围的右边界,表示[0,right]范围内是有效的ends,其余是[ends+1,arr.length]是无效的
时间复杂度O(N*logN)生成dp数组的过程是利用二分查找来进行的优化。
- 生成一个长度为N的数组ends,初始时ends[0]=arr[0],其他位置上的值为0。
- 生成整型变量right,初始时right=0。
在从左到右遍历arr数组的过程中,求解dp[i]的过程需要使用ends数组和right变量
遍历的过程中, ends[0..right]为有效区, ends[right+1..N-1]为无效区。
对于有效区上的位置b,如果有ends[b]==c,则表示遍历到目前为止,在所有长度为b+1的递增序列中,最小的结尾数是c。无效区的位置则没有意义。
例如arr=[2,1,5,3,6,4,8,9,7],初始时dp[0]=1, ends[0]=2, right=0。
ends[0..0]为有效区,ends[0]==2的含义是,在遍历过arr[0]之后,所有长度为1的递增序列中(此时只有[2]),最小的结尾数是2。
之后的遍历继续用这个例子来说明求解过程。
1,遍历到arr[1]==1。 ends有效区=ends[0..0]=[2],在有效区中找到最左边的大于等于arr[1]的数。
发现是ends[0],表示以arr[1]结尾的最长递增序列只有arr[1],所以令dp[1]=1。
然后令ends[0]=1,因为遍历到目前为止,在所有长度为1的递增序列中,最小的结尾数是1而不再是2了。
2,遍历到arr[2]==5。 ends有效区=ends[0..0]=[1],在有效区中找到最左边的大于等于arr[2]的数。
发现没有这样的数,表示以arr[2]结尾的最长递增序列长度=ends有效区长度+1,所以令dp[2]=2。
ends整个有效区都没有例arr[2]更大的数,说明发现了例ends有效区长度更长的递增序列,于是把有效区扩大, ends有效区=ends[0..1]=[1,5]。
3,遍历到arr[3]==3。 ends有效区=ends[0..1]=[1,5],在有效区中用二分法找到最左边的大于等于arr[3]的数。
发现是ends[1],表示以arr[3]结尾的最长递增序列长度为2,所以令dp[3]=2。然后令ends[1]=3,因为遍历到目前为止,在所有长度为2的递增序列中,最小的结尾数是3而不再是5了。
4,遍历到arr[4]==6。 ends有效区=ends[0..1]=[1,3],在有效区中用二分法找到最左边的大于等于arr[4]的数。
发现没有这样的数,表示以arr[4]结尾的最长递增序列长度=ends有效区长度+1,所以令dp[4]=3。 ends整个有效区都没有例arr[4]更大的数,说明发现了例ends有效区长度更长的递增序列,于是把有效区扩大, ends有效区=ends[0..2]=[1,3,6]。
5,遍历到arr[5]==4。 ends有效区=ends[0..2]=[1,3,6],在有效区中用二分法找到最左边的大于等于arr[5]的数。
发现是ends[2],表示以arr[5]结尾的最长递增序列长度为3,所以令dp[5]=3。然后令ends[2]=4,表示在所有长度为3的递增序列中,最小的结尾数变为4。
6,遍历到arr[6]==8。 ends有效区=ends[0..2]=[1,3,4],在有效区中用二分法找到最左边的大于等于arr[6]的数。
发现没有这样的数,表示以arr[6]结尾的最长递增序列长度=ends有效区长度+1,所以令dp[6]=4。 ends整个有效区都没有例arr[6]更大的数,说明发现了例ends有效区长度更长的递增序列,于是把有效区扩大, ends有效区=ends[0..3]=[1,3,4,8]。
7,遍历到arr[7]==9。 ends有效区=ends[0..3]=[1,3,4,8],在有效区中用二分法找到最左边的大于等于arr[7]的数。
发现没有这样的数,表示以arr[7]结尾的最长递增序列长度=ends有效区长度+1,所以令dp[7]=5。 ends整个有效区都没有例arr[7]更大的数,于是把有效区扩大, ends有效区=ends[0..5]=[1,3,4,8,9]。
8,遍历到arr[8]==7。 ends有效区=ends[0..5]=[1,3,4,8,9],在有效区中用二分法找到最左边的大于等于arr[8]的数。
发现是ends[3],表示以arr[8]结尾的最长递增序列长度为4,所以令dp[8]=4。然后令ends[3]=7,表示在所有长度为4的递增序列中,最小的结尾数变为7。
具体过程请参看如下代码中的getdp2方法。