经典算法 | 求解最大连续子段O(nlogn)时间复杂度的算法

传统的求某个序列的最长递增子序列的题目,假如使用一般的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

猜你喜欢

转载自blog.csdn.net/u012737193/article/details/80155830