python-最长上升子序列(LIS)

最长上升子序列(LIS):

对于给定的一个序列(a1, a2, …, aN),我们也可以从中得到一些上升的子序列(ai1, ai2, …, aiK),这里1 <= i1 < i2 < … < iK <= N,其中最长的子序列就是最长上升子序列,本文求最长上升子序列的长度

举例:

序列:1 2 7 3 5 4

可得到的子序列:(1 2 7) (1 2 3 5) (1 2 3 4) (2 7) (2 3 5) (2 3 4) (7) (3 5) (3 4) (5) (4)

则该序列的最长上升子序列的长度为4,有两个分别为(1 2 3 5) (1 2 3 4)

LIS长度的求解方法有三种:

1.O(n^2)的DP

2.O(nlogn)的二分+贪心法

3.O(nlogn)的树状数组优化的DP

第一种:O(n^2)的DP

动态规划的解法,思路为:当求n长序列LIS的长度时,先求n-1长序列LIS的长度,再与第n个数比较。求前n-1个数的最长上升子序列,可以通过求前n-2个数的最长上升子序列……直到求前1个数的最长上升子序列,此时LIS当然为1。

假设a数组为序列数组,a[ i ]表示序列中下标 i 对应的数;f 数组用来保存子序列长度,f[ i ]表示以a[ i ]作为最后一位数的子序列的长度。

将a[ i ]与前i-1位数比较,如果a[ i ]大于之前的某个数a[ j ],则f[ j ]+1,再与f[ i ]比较,取最大值。

例如:1 2 7 3 5 5

前1个数,a[1] = 1                        f[1] = 1                               子序列为1   

前2个数,a[2] = 2    1 < 2            f[2] = f[1] + 1 = 2                子序列为1 2

前3个数,a[3] = 7    2 < 7            f[3] = f[2] + 1 = 3                子序列为1 2 7

前4个数,a[4] = 3    2 < 3 < 7      f[4] = f[2] + 1 = 3                子序列为1 2 3

前5个数,a[5] = 5    3 < 5 < 7      f[5] = f[4] + 1 = 4                子序列为1 2 3 5

前6个数,a[6] = 5    5 = 5            f[6] = f[5] = 4                      子序列为1 2 3 5

max(f)即为所求LIS长度

n = int(input())
a = list(map(int,input().split()))
f = [1]*n
for i in range(n): # i代表当前dp的数
    for j in range(i): # j代表[0,i-1]区间的数
        if a[i] > a[j]:
            f[i] = max(f[i],f[j]+1)
        elif a[i] == a[j]:
            f[i] = max(f[i],f[j])
        else:
            pass
ans = max(f)
print(ans)

第二种:O(nlogn)的二分+贪心法

假设a数组为序列数组,a[ i ]表示序列中下标 i 对应的数;b数组用来保存相同长度子序列最后一位最小值,b[ i ]表示 i 长的所有子序列最后一位的最小值。对于一个上升子序列,显然其结尾元素越小,越有利于在后面接其他的元素,也就越可能变得更长。

因此,我们只需要维护 b 数组,对于每一个a[ i ],如果a[ i ] > b [当前最长的LIS长度],就把 a[ i ]接到当前最长的LIS后面,即b [当前最长的LIS长度 + 1] = a [ i ]。 如果等于就跳过。如果小于,就用二分法从b数组中找到第一个大于等于a [ i ]的元素b[ j ],用a [ i ]去更新b[ j ]。

例如:1 2 7 3 5 5

a[1] = 1                        b[1] = 1                               b为1   

a[2] = 2    1 < 2            b[2] = 2                               b为1 2

a[3] = 7    2 < 7            b[3] = 7                               b为1 2 7

a[4] = 3    2 < 3 < 7      b[3] = 7                               b为1 2 3

a[5] = 5    3 < 5 < 7      b[4] = 5                               b为1 2 3 5

a[6] = 5    5 = 5            continue                              b为1 2 3 5

b[-1]即为所求

import sys
b = [sys.maxsize]*(n+1)  # [1,n]
def binary_search(lis,lis_effective_len,p): # 返回比a[i]大或等于的数的下标
    l,r = 1,lis_effective_len
    while r >= l:    # [8,9] 8 --> 8 == 8 , r - 1 ,[8] --> 8 == 8 , r - 1 , l = r + 1 , lis[l] = 8
        mid = (l + r)//2
        if lis[mid] >= p:
            r = mid - 1
        else:
            l = mid + 1
    return l
b[1] = a[0]
ans = 1
for i in range(n):
    if a[i] > b[ans]:
        ans += 1
        b[ans] = a[i]
    elif a[i] == b[ans]:
        continue
    else:
        b[binary_search(b,ans,a[i])] = a[i]
print(ans)

使用bisect库(二分库),bisect_left(ls, x)返回大于等于x的下标

from bisect import bisect_left
b = [a[0]]  
for i in range(n):
    if a[i] > b[-1]:
        b.append(a[i])
    elif a[i] == b[-1]:
        continue
    else:
        b[bisect_left(b,a[i])] = a[i]
print(len(b))

第三种:O(nlogn)的树状数组优化的DP

参考:最长上升子序列 (LIS) 详解+例题模板 (全)-CSDN博客

  • 17
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值