最长上升子序列(LIS)及其优化O(nlongn)

按照数据结构来划分的话,最长上升子序列(LIS)是属于线性DP,其作为动态规划经典入门模型,重要性也是不言而喻的。而LIS问题也有很多种变型,对于这些问题有的超过了动态规划的适用范围,有的需要对转移方程进行更改,需要具体问题具体分析。

前置知识:
    - 子序列的概念
    - 动态规划一般步骤
    - 二分查找

问题模型:给定一个长度为N的数列A,求数值单调递增的子序列长度最长是多少。

解题思路:
状态表示:我们设F[i]表示以A[i]为结尾的“最长上升子序列”的长度。
阶段划分:子序列的结尾位置(数列A中的位置,从前到后)。
转移方程:F[i] = max{ f[j] + 1} (0 <= j < i 且 A[j] < A[i])。
边界:F[0] = 0。
目标:max{F[i]} ( 0 <= i < = N)。

如此设计目标函数与转移方程,很显然可以得到正确结果,其效率是O(N^2),代码实现也很简单。

优化:
首先考虑一下上述算法最耗时的地方在哪,显然我们每次都要用O(N)的时间遍历前i-1个位置上的元素来更新F[i],而能更新F[i]的元素A[j]需要满足两个条件:A[j] < A[i]且F[j] >= F[i],那么F[i]就可以被更新为F[j]+1。在这个过程中,A[j]也可以描述为“小于A[i]的所有元素中F的值最大的那个”,通过这个描述来找A[j]的效率是O(N)。
考虑另一种方式,我们新增一个辅助数组d,d[i] 表示“长度为i的最长上升子序列的最小结尾是多少”,那么很显然,d数组中的元素是严格递增的。且每当我们读取到A序列中的一个元素A[i]时,都有两种情况:
    1. A[i] > d[len],此时 d[++len] = A[i]。 
    2. A[i] <= d[len],此时说明d数组中某个元素可以被A[i]更新,我们采用二分查找的方式更新d数组即可。

其中len代表当前最长上升子序列的长度。
上述方法中,如果遇到情况1,那么效率是O(1),情况2,效率就是O(log N),总体来讲,对于大量数据,该优化效果还是很喜人的。

优化的正确性说明:
上述优化可以用堆栈思想来解释,也可以将其看作一个小技巧。借助于d数组,对于序列A中的每个元素A[i],我们都可以快速找到“小于A[i]的所有元素中F的值最大的那个”,**因为d[len]存放的就是最长上升子序列长度为len时的最小元素** 。如果A[i] <= d[len],那么很显然当前的最长上升子序列长度len不能再增加了(因为A[i] 不能添加在A的子序列d[1] ~d[len] 的后面),且d数组可以被更新(再不济也可以将d[len] 更新为 A[i],因为 A[i] < d[len])。于是借助于d数组的特性,完成了优化,当然也有所牺牲。

代码示例:
 

int d[100005];
int b_search(int x,int s,int e){
    while(s < e){
        int mid = s+(e-s)/2;
        if(d[mid] >= x) e = mid;
        else s = mid+1;
    }
    return s;
}
int LIS(int A[],int n){
    memset(d,0,sizeof d);
    int len = 1;
    d[1] = A[0];
    for(int i = 1;i < n;i++){
        if(A[i] >= d[len])  d[++len] = A[i];
        else{
            int p = b_search(A[i],1,len);
            d[p] = A[i];
        }
    }
//  for(int i = 0;i < len;i++)  cout << d[i+1]<<" ";
    return len;
}

##### 参考资料

- 《算法竞赛进阶指南》,李煜东,P237.
- [博客](https://www.cnblogs.com/wxjor/p/5524447.html)

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

迷亭1213

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

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

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

打赏作者

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

抵扣说明:

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

余额充值