2022年第十三届蓝桥杯 python B组 第I题 最长不下降子序列

想了解更多请查看:https://blog.csdn.net/qq_55364042/article/details/130098343?spm=1001.2014.3001.5501

由于在csdn上发现的各种大佬写的题解在测试网站中都是错的或者最多跑n<100的案例,所以我只能自己研究该题,在苦苦思索后我终于有了一套自己的方法,在测试网站中可以通过所有案例。

其实这道题有一个更简单的版本,在左程云写的《程序员代码面试指南》——最长递增子序列

我在简单描述这本书中的这个题之前,我们应该先明确什么是子序列,子序列就是一个序列中抽出来的(未必连续),如 1 4 2 8 5 中子序列有:1 4,4 2,1 4 5,...,2 8..等等

最长递增子序列就是在一个序列中找出最长的递增子序列的长度,案例 1 4 2 8 5:最长递增子序列为:1 4 5、1 4 8、1 2 8、 1 2 5,最长递增子序列长度为:3。

我来简单描述一下书中的最优做法,建立一个列表比如dp=[],然后遍历数组 【1,4,2,8,5】,(1)遍历到1 dp=[1],#已知的最长子序列长度为1,长度为1的最长递增子序列中最小的结尾数字是1。

(2)遍历到4 dp=[1,4]#已知的最长子序列长度为2,长度为2的最长递增子序列中最小的结尾数字是4,长度为1的最长递增子序列中最小的结尾数字是1。

(3)遍历到2 dp=[1,2]#已知的最长子序列长度为2,长度为2的最长递增子序列中最小的结尾数字是2,长度为1的最长递增子序列中最小的结尾数字是1。

(4)遍历到8 dp=[1,2,8]#已知的最长子序列长度为3,长度为3的最长递增子序列中最小的结尾数字是8,长度为2的最长递增子序列中最小的结尾数字是2....。

(5)遍历到5 dp=[1,2,5]#已知的最长子序列长度为3,.....。

以上做法会用到二分查找,时间复杂度为O(nlogn),详情可以去翻阅此书

现在我们来描述一下蓝桥杯中的最长不下降子序列,它与上一题的区别 第一它是'不下降子序列'也就是说重复也可以,第二它需要修改连续k个元素为任意数字

先说一个粗略的做法,将k想象为一个可以覆盖元素的滑动窗口,自左往右滑动,k每滑动一次我们就从没有被k覆盖的元素中找最长不下降子序列。而k自左往右滑动完,出现过的最长的最长不下降子序列的长度+k就是我们要的答案

案例 :1,4,2,8,5   。k=2     max=0

(1)k覆盖1,4。从剩余的2,8,5找得最长不下降子序列,2,8。或2,5。max=2

(2)k覆盖4,2。从剩余的1,8,5找得最长不下降子序列 1,8。1,5。max不变

(3)k覆盖2,8。从剩余的1,4,5找得最长不下降子序列1,4,5。max=3

(4)k覆盖8,5。.........max不变

最终max+k就是我们要的答案

现在就让我们想想有什么可以优化的地方吗,我发现其实没必要k每滑动一次就将所有剩余元素全跑一遍的,说明如下:

k每滑动一次就会有一个元素被盖住还会有一个元素露出来,被该住的元素意味着某些子序列变短了,比如1,4,2,8,5,中子序列1,4。1,4,8。1,4,5。....4如果被盖住就意味着它们变短了! 而我们要的是最长的,所以这个被盖住的元素对我们来说没有任何价值!!重点在那个新露出来的元素,它意味着某些子序列变长了,而那些变长的子序列中最长的那个就可能是我们要的答案!!!我们将在k每次滑动时新露出来的元素所在的最长的不下降子序列上做文章。

max=0

第一步

预处理:首先使用书中的做法(反向)跑出每个能成为新露出元素(0~len-k),以它开头(不包括它)右边隔着k的最长不下降子序列的长度并保存。案例 1,4,2,8,5,k=2 。1:【8或5】=1 。  4:【5】=1。2:【】=0

第二步

使用书中做法跑出每个能成为新露出元素(0~len-k),以它结尾的最长不下降子序列的长度并加上其预处理的结果然后对比max。案例 1,4,2,8,5 。1:【1】=1,1+1的预处理=2,max=2。4:【1,4】=2 ,2+4的预处理=3, max=3。2:【1,2】=2......max不变

最终结果max+k=5

以下为代码


##########################输入部分
n, k = map(int, input().split())
arr = [int(i) for i in input().split()]

l = n
notk_max = 0
zd = [0 for i in range(l)]

######################################代码部分

def bs(t,a,mid):
    if t:
        return mid <= a
    else:
        return mid >= a


def erf(t,dp,a):#找到新元素在单调栈中的位置,t为False是自左往右第一个小于a,t为True是自左往右第一个大于a
    if not dp:
        return -1
    l,r=0,len(dp)-1
    while l<r:
        mid=(l+r)//2
        if bs(t,a,dp[mid]):#缩头
            l=mid+1
        else:
            r=mid
    return -1 if bs(t,a,dp[l]) else l

def zhao2():  # 得出以我结尾的的最长子序列
    global zd, notk_max, arr, k, l
    dp = []
    for i in range(l - k):
        wz = erf(True, dp, arr[i])
        if wz < 0:
            dp.append(arr[i])
            zd[i] += len(dp)
        else:
            dp[wz] = arr[i]
            zd[i] += (wz + 1)
        if zd[i] > notk_max:
            notk_max = zd[i]


def zhao1():  # 得出以我开头的最长子序列 (不包括自己),以及k在最左侧的not_kmax
    global zd, notk_max, arr, k, l
    dp = []
    for i in range(l - 1, k, -1):
        wz = erf(False, dp, arr[i])
        if wz < 0:
            dp.append(arr[i])
        else:
            dp[wz] = arr[i]
        #######以下为zd赋值
        wz = erf(False, dp, arr[i - k - 1])
        if wz < 0:
            zd[i - k - 1] = len(dp)
        else:
            zd[i - k - 1] = wz
    ########以下得出k覆盖最左 最长不下降子序列长度
    notk_max = len(dp)
    wz = erf(False, dp, arr[k])
    if wz < 0:
        notk_max += 1


##############################################输出部分
zhao1()
zhao2()
print(notk_max + k)

  • 16
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 14
    评论
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

自 在

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值