zoukankan      html  css  js  c++  java
  • 动态规划—最长上升子序列问题(LIS)

    1. 子序列和子串的区别

    • 子序列(subsequene):子序列并不要求连续,例如:序列[4, 6, 5]是[1, 2, 4, 3, 7, 6, 5]的一个子序列;
    • 子串(substring、subarray):子串一定是原始字符串的连续子串。

    2. 最长上升子序列 (可不连续)

    题目

    方法1、暴力解法

    可以首先计算出数组的所有子序列,时间复杂度度为(O(2^N)),再对子串依次判定是否为递增,时间复杂度为o(n),所以总的时间复杂度为(O(N.2^N))

    方法2、动态规划

    1.定义状态

    dp[i]表示以nums[i]为结尾的最长上升子序列的长度。

    2.状态转移方程

    只要nums[i]严格大于在它位置之前的某个数,则nums[i]就可以接在该数后面形成一个更长的一个上升子序列。

    [dp[i] = egin{cases} max_{j} dp[j] + 1, & ext{0<=j<i,nums[j]<nums[i]} \ 1, & ext{其他} end{cases} ]

    3.初始化

    dp[i]=1,1个字符初始都为长度为1的上升子序列。

    4. 输出

    状态数组dp的最大值才是整个数组的最长上升子序列的长度。

    [max_{{1<=i<=n}} dp[i] ]

    5.时间复杂度和空间复杂度

    时间复杂度:(O(N^2)),首先要遍历数组中每一个数i,复杂度为N,然后需要判定i之前的dp[j]的最大,j最糟情况复杂度也为N,因此总的复杂度为(o(N^2))
    空间复杂度度:(O(N)),需要维护一个状态数组dp。

    def lengthoflis(nums):
        if not nums:
            return 0
        n = len(nums)
        dp = [1 for _ in range(n)]
        for i in range(1,n):
            for j in range(i):
                if nums[j] < nums[i]:
                    dp[i] = max(dp[i],dp[j]+1)
        return max(dp)
    

    方法3、贪心+二分查找

    一个简单的贪心算法,如果我们想要上升子序列尽可能的长,则我们希望让序列上升的尽可能慢,也就是每次在上升子序列最后加上的那个数尽可能的小。

    1.定义状态

    d[i] 表示长度为 i+1 的最长上升子序列的末尾元素的最小值。

    证明 d[i]关于i是单调递增的。

    因为如果d[j]>d[i]且j<i,我们从长度为i的最长上升子序列中删除j-i个元素,使得序列长度与j一致。由于该序列严格上升所以d[j]<d[i],则与假设矛盾,因此d的单调性得证。

    2.初始化

    用len记录目前最长上升子序列的长度,起始时len为1,i=0,则d[0] = num[0]

    3. 算法流程

    依次遍历数组nums的每个元素,并更新数组d和len的值。

    • 如果nums[i] > d[len] ,则直接加入d数组末尾,并更新len = len+1
    • 否则,在d数组中二分查找,找到第一个比nums[i]小的数d[k],并更新d[k+1] = nums[i].

    4.输出

    有序数组d的长度,就是所求的最长上升子序列的长度

    5.复杂度分析

    时间复杂度度:O(NlogN),遍历数组使用了O(N),二分查找法使用了O(logn)。
    空间复杂度:O(N),要维护状态数组d。

    def lengthoflis(nums):
        if not nums:
            return 0
        d[0] =  num[0]
        for i in range(1,len(nums)):
            if i>d[-1]:
                d.append(i)
            else:
                l, r = 0, len(d)-1
                loc = r
                while l <=r:
                    mid = (l+r)//2
                    if d[mid] >= i:
                        loc = mid
                        r = mid -1
                    else:
                        l = mid + 1
                d[loc] = n
        return len(d)
    

    3. 最长上升子串

    方法1、暴力遍历

    从数组第一数开始遍历,求以该数为初始值的最长递增序列。 时间复杂度度为(O(N^2))

    方法2、动态规划

    1. 定义状态

    dp[i]表示以nums[i]为结尾的最长上升子序列的长度。

    2.初始化

    dp = dp[1]* n ,初始化都为1

    3. 状态转移方程

    [dp[i] = egin{cases} dp[i-1]+1, & ext{if nums[i] > nums[i-1]} \ 1, & ext{其他} end{cases} ]

    4.输出

    状态数组dp的最大值才是整个数组的最长上升子序列的长度。

    [max_{{1<=i<=n}} dp[i] ]

    5.复杂度分析

    时间复杂度:(O(N)),遍历数组中每一个数 。
    空间复杂度度:(O(N)),需要维护一个状态数组dp。

    def lengthoflis(nums):
        if not nums:
            return 0
        n = len(nums)
        dp = [1 for _ in range(n)]
        for i in range(1,n):
            if nums[i] > nums[i-1]:
                dp[i] = dp[i-1]+1
        return max(dp)
    
  • 相关阅读:
    windows7修改双系统启动项名称、先后顺序、等待时间
    windows初始化后做了哪些事情
    我的wordpress插件总结
    分析MySQL慢日志(转)
    在Java中使用Memcached(转)
    memcached应用场景(转)
    memcached简介(转)
    Linux下memcache的安装和启动(转)
    列式数据库
    Android测试(一):在Android中测试App
  • 原文地址:https://www.cnblogs.com/laiyaling/p/14515067.html
Copyright © 2011-2022 走看看