最长上升子序列(LIS)

最长上升子序列(LIS)

  • 给定长度为\(N\)的序列\(a_i\),求序列中一个长度最长且递增的子序列。输出子序列的长度。\(N\leq 1000\)

\(O(N^2)\)解法

  • \(f[i]\)为以\(a_i\)结尾的最长上升子序列的长度

    易知\(f[i]\)的最大值为 区间\([1,i)\)中的 小于\(a_i\)的最大的\(j\)加上1

    意思就是,

    假设\(a_j\)为区间\([1,i)\)中小于\(a_i\)的最大值,那么\(a_i\)就可以拼在\(a_j\)的后面,这时以\(a_i\)结尾的LIS的长度就为以\(a_j\)结尾的LIS长度+1

    用状态转移方程表示就是

    \(\large{f[i]=\displaystyle\max_{j=1}^{i-1}(f[j])+1,a_i>a_j}\)

  • 用代码表示就是

    int A[N+1];
    int f[N+1];
    for(int i = 1;i<=N;i++){
        for(int j = 1;j<i;j++){
            if(A[i] <= A[j]) continue;
            f[i] = max(f[i],f[j]+1);
        }
    }
    int max_ = -1;
    for(int i = 1;i<=N;i++) max_ = max(f[i],max_);
    

\(O(N\log N)\)解法

  • \(f[i]\)为长度为\(i\)的上升子序列的最后一个数字

    \(a_j\)为序列的第\(j\)个元素

    \(f_{i}\leq a_j < f_{i+1}\),则将\(f_i\)的值更新为\(a_j\)

    相当于将长度为\(i\)的上升子序列的最后一位\(f_i\)改为了更小的\(a_j\),显然这样更有利于在后面接其他元素

  • 但是目前遍历长为\(n\)的序列,对于其中每项\(a_j\),都需要进行至多\(i\)次操作去寻找合适的区间,时间复杂度仍然为\(O(N^2)\)

  • 但是,由于\(f_i\)单调递增,可以在查找的时候使用二分查找来优化,这样就至多进行\(\log N\)次查找,时间复杂度降为\(O(N\log N)\)

  • 代码实现

    int A[N+1];
    int f[N+1];
    int cnt = -1;
    //cnt代表当前最长上升子序列的长度
    fill(f+1,f+N+1,1e9);
    //fill(st,ed,val)意为用val填充数组[st,ed)区间
    //使用极大值填充区间
    for(int i = 1;i<=N;i++){
        int j = lower_bound(f+1,f+N+1,A[i]) - f;
        //lower_bound(st,ed,val)意为在[st,ed)区间内查找第一个不小于val的元素
        //lower_bound返回一个指向找到元素的迭代器
        //减去的f是地址
        //地址 - 地址 = 下标
        cnt = max(cnt,j);
        f[j] = A[i];
    }
    int ans = cnt;
    
posted @ 2022-02-10 22:52  Burnling  阅读(82)  评论(0编辑  收藏  举报