动态规划—最长上升子序列问题(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] = \begin{cases} max_{j} dp[j] + 1, & \text{0<=j<i,nums[j]<nums[i]} \\ 1, & \text{其他} \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] = \begin{cases} dp[i-1]+1, & \text{if nums[i] > nums[i-1]} \\ 1, & \text{其他} \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)
posted @ 2021-03-10 23:31  亚北薯条  阅读(389)  评论(0编辑  收藏  举报