• 一道有趣的最长子序列问题


    一道有趣的最长子序列问题 – 潘登同学的金融经济学笔记

    来源

    前几天在刷视频的时候,发现了这样一道题

    在这里插入图片描述

    所谓子序列就是一个序列 a i 1 , a i 2 , ⋯   , a i n a_{i1},a_{i2},\cdots,a_{in} ai1,ai2,,ain满足 i 1 < i 2 < ⋯ < i n i1< i2 < \cdots < in i1<i2<<in,并不要求 i 1 , i 2 , ⋯   , i n i1,i2,\cdots,in i1,i2,,in是紧邻的,只要求下标单调递增即可;

    一个具体例子:

    • 一个序列 1 , 5 , 2 , 4 , 3 1,5,2,4,3 1,5,2,4,3
    • 其中存在单调递增子序列 { 1 , 5 } , { 1 , 2 , 4 } , { 1 , 2 , 3 } , { 2 , 3 } \{1,5\},\{1,2,4\},\{1,2,3\},\{2,3\} {1,5},{1,2,4},{1,2,3},{2,3}
    • 其中存在单调递减子序列 { 5 , 2 } , { 5 , 4 } { 5 , 4 , 3 } \{5,2\},\{5,4\}\{5,4,3\} {5,2},{5,4}{5,4,3}

    求解

    数学证明的方法,显然我是不太会,还请各位大神赐教; 但是基于置信度的解法,我还是会一点滴;

    递推公式

    用一个记号 ( x , y ) (x,y) (x,y)表示,从某个数开始,x表示从该数开头的最长递增序列,y表示从该数开始最长的递减序列;

    从长度为2的序列说起

    • 显然,要么是递增,要么是递减序列

    到长度为3的序列

    • 最后一个数的记号 ( 1 , 1 ) (1,1) (1,1)
    • 倒数第二个数的记号
      • 若:倒数第二个数比最后一个数小, 记为 ( 2 , 1 ) (2,1) (2,1)
      • 若:倒数第二个数比最后一个数大, 记为 ( 1 , 2 ) (1,2) (1,2)
    • 倒数第三个数(第一个数)的记号
      • 在倒数第二个数的记号为 ( 2 , 1 ) (2,1) (2,1)的前提下:
        • 若:倒数第三个数比倒数第二个数小, 记为 ( 3 , 1 ) (3,1) (3,1)
        • 若:倒数第三个数比倒数第二个数大, 记为 ( 2 , 2 ) (2,2) (2,2)
      • 在倒数第二个数的记号为 ( 1 , 2 ) (1,2) (1,2)的前提下:
        • 若:倒数第三个数比倒数第二个数小, 记为 ( 2 , 2 ) (2,2) (2,2)
        • 若:倒数第三个数比倒数第二个数大, 记为 ( 1 , 3 ) (1,3) (1,3)

    到长度为4的序列(相当于在长度为3的序列前加一个数)

    • 倒数第三个数记号为 ( 3 , 1 ) (3,1) (3,1)

      • 若倒数第四个数比倒数第三个数小,记为 ( 4 , 1 ) (4,1) (4,1)
      • 若倒数第四个数比倒数第三个数大,记为 ( 3 , 2 ) (3,2) (3,2)
    • 倒数第三个数记号为 ( 2 , 2 ) (2,2) (2,2),因为 ( 2 , 2 ) (2,2) (2,2)有两种情况

      • 第一种情况,在倒数第二个数的记号为 ( 2 , 1 ) (2,1) (2,1)且倒数第三个数比倒数第二个数大
        • 若倒数第四个数比倒数第三个数小且比倒数第二个数小,记为 ( 3 , 2 ) (3,2) (3,2)

        • 若倒数第四个数比倒数第三个数小且比倒数第二个数大,记为 ( 2 , 2 ) (2,2) (2,2)
          在这里插入图片描述

        • 若倒数第四个数比倒数第三个数大,记为 ( 2 , 3 ) (2,3) (2,3)

      • 第二种情况,在倒数第二个数的记号为 ( 1 , 2 ) (1,2) (1,2)且倒数第三个数比倒数第二个数小
        • 若倒数第四个数比倒数第三个数小,记为 ( 3 , 2 ) (3,2) (3,2)
        • 若倒数第四个数比倒数第三个数大且比倒数第二个数小,记为 ( 2 , 2 ) (2,2) (2,2)
        • 若倒数第四个数比倒数第三个数大且比倒数第二个数大,记为 ( 2 , 3 ) (2,3) (2,3)
          在这里插入图片描述
    • 倒数第三个数记号为 ( 1 , 3 ) (1,3) (1,3)

      • 若倒数第四个数比倒数第三个数小,记为 ( 2 , 3 ) (2,3) (2,3)
      • 若倒数第四个数比倒数第三个数大,记为 ( 1 , 4 ) (1,4) (1,4)

    当然了,这样的递推能无限进行下去,但是我们还是想从中找到规律,当序列比较短(2,3左右)的时候,我们似乎只需要比较这个数与后一个数的大小关系,一旦序列变长了之后,我们不仅需要比较这个数与下一个数的大小关系,还需要比较这个数与后两个数的大小关系,而且还是有时候需要比较而有时又无需比较,我们需要总结一个递推式;我们将目光放到我们的记号 ( x , y ) (x,y) (x,y)上;

    • 对一个数来说,x表示从该数开头的最长递增序列,y表示从该数开始最长的递减序列;
      • 从这个数往后找,如果这个数小于找到的下一个数,那么自然能将记号x加一,表述为数学语言
        ∀ a i , ∃ a i + k , 若 a i < a i + k x i ≥ x i + k + 1 \forall a_i, \exist a_{i+k},若 a_i < a_{i+k} \\ x_i \geq x_{i+k} + 1 ai,ai+k,ai<ai+kxixi+k+1
      • 所以关键是要找到这个数往后大于该数的数的最大的记号x和这个数往后小于该数的数的最大记号y

    那么很自然地,我们就去对这个数与其后的数进行比大小,就能确定该数的记号;那么对每个数都与后面的数比一次,粗略来看算法复杂度就是 O ( n 2 ) O(n^2) O(n2)

    算法实现

    import random
    num = 4
    a = [random.randint(0,400) for _ in range(num)]
    print('数据len:',len(a))
    print(a)
    
    dp_x = [1 for _ in range(num)] # 记号(x,y)
    dp_y = [1 for _ in range(num)] # 记号(x,y)
    for i in range(num):
        index = num-1 - i # 表示现在这个数的索引
        for j in range(index+1,num):
            # 表示找到了目前为止最大的x
            if a[index] <= a[j] and dp_x[index] <= dp_x[j]:
                dp_x[index] = dp_x[j] + 1
            # 表示找到了目前为止最大的y
            if a[index] >= a[j] and dp_y[index] <= dp_y[j]:
                dp_y[index] = dp_y[j] + 1
    
    print('最长递增子序列的长度为:',max(dp_x))
    print('最长递减子序列的长度为:',max(dp_y))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    算法非常简单啊, 非常长的序列我们很难验证,但是验证长度为4的序列就足够了,下面是几次运行的结果,能看出与我们的分析是一致的

    在这里插入图片描述

    接着我们来计算序列为300长度的最长子序列

    import random
    for _ in range(1000):
        result = []
        num = 300
        a = [random.randint(0,1000) for _ in range(num)]
        # print('数据len:',len(a))
        # print(a)
    
        dp_x = [1 for _ in range(num)] # 记号(x,y)
        dp_y = [1 for _ in range(num)] # 记号(x,y)
        for i in range(num):
            index = num-1 - i # 表示现在这个数的索引
            for j in range(index+1,num):
                # 表示找到了目前为止最大的x
                if a[index] <= a[j] and dp_x[index] <= dp_x[j]:
                    dp_x[index] = dp_x[j] + 1
                # 表示找到了目前为止最大的y
                if a[index] >= a[j] and dp_y[index] <= dp_y[j]:
                    dp_y[index] = dp_y[j] + 1
    
        # print('最长递增子序列的长度为:',max(dp_x))
        # print('最长递减子序列的长度为:',max(dp_y))
        result.append(max(dp_x))
        result.append(max(dp_y))
    print('1000次循环中,300长度的序列中最短的最长子序列的长度为:',min(result))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    在这里插入图片描述

    显然经过1000次的模拟,得到的最短的最长子序列也是27,所以有99.99%的把握认为能找到一个长度为17的单调子序列;

  • 相关阅读:
    shell 变量 入门
    java-net-php-python-50ssm高校大学生创新创业管理系统三篇文档计算机毕业设计程序
    二十三、java版 SpringCloud分布式微服务云架构之 Java 多态
    OpenCV实战(20)——图像投影关系
    Java中ArrayList和LinkedList区别
    捷报|数说故事同期斩获虎啸奖、弯弓奖六项大奖
    Spring注解
    数学建模不会 LaTex 排版 | 教你如何在 Word 中优雅地使用漂亮的 LaTex 公式
    操作系统学习笔记
    建立表使用约束
  • 原文地址:https://blog.csdn.net/weixin_52185313/article/details/128088933