传统的求某个序列的最长递增子序列的题目,假如使用一般的DP去做的话时间或达到O(n ^ 2),,要求使用O(nlog(n))的时间解决题目,因此我们需要考虑一种特殊的DP方法来解决题目
定义一个数组Dp,假设数组里面有Dp[1 ~ n]的值,给定的数组为nums,Dp[1 ~ n]对应于nums[1 ~ m]的范围,Dp[i]的具体含义为,在nums[1~m]的范围取递增子序列,可以找到的所有长度为i的子序列中末尾元素最小的伪Dp[i],
因此可以很容易的知道,len(Dp)表示nums[1 ~m]范围的最长子序列的长度,Dp[n]表示所有最长子序列中末尾的最小元素
知道了这一点之后我们可以遍历nums,动态的改变Dp,假设这个时候Dp里面有n个元素,下面分三种情况考虑
1) 当nums[i] > Dp[n]的时候,最长子序列的长度加一,最长子序列长为n +1,且长度为n + 1的子序列中末尾元素最小的是nums[i],因此Dp[n + 1] = nums[i]
2) 当nums[i] < Dp[0]的时候,nums[i]比Dp里面的所有元素都要小,因此更新Dp里面长度为1的最长子序列中的末尾最小的元素,也就是Dp[0] = nums[i]
3) 当Dp[0] < nums[i] < Dp[n]的时候,使用二分搜索在Dp[1 ~ n]的范围中找到一个j,使得theDp[j] >nums[i],theDp[j – 1] <nums[i],这个时候可以证明,nums[i]的值为长度为j的子序列中新的最小的末尾元素,也可以证明,nums[i]不会改变theDp[1 ~ j-1]和theDp[j+1 ~ n]的元素,只需要改变theDp[j]的元素
另外特别需要注意一点就是,当nums[i]和theDp[]中任何一个元素相等的时候,直接跳过这个数字,因为nums[i]不可能改变当前范围任何长度的递增子序列的最小的末尾值
该算法在最坏情况下时间复杂度为O(nlongn)
class Solution(object):
def lengthOfLIS(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
theL = len(nums)
if theL <= 0: return 0
theDp = [0 for i in range(theL + 1)]
index = 0
theDp[0] = nums[0]
for i in range(theL):
if nums[i] > theDp[index]:
index += 1
theDp[index] = nums[i]
elif nums[i] < theDp[0]:
theDp[0] = nums[i]
else:
if nums[i] == theDp[0] or nums[i] == theDp[index]: continue
left = 0
right = index
while True:
mid = (right + left) // 2
if theDp[mid] == nums[i]: break
elif theDp[mid] < nums[i]:
if nums[i] < theDp[mid + 1]:
theDp[mid + 1] = nums[i]
break
left = mid + 1
else:
if nums[i] > theDp[mid - 1]:
theDp[mid] = nums[i]
break
right = mid - 1
return index + 1