已知集合A[1...n],求最长上升(递增、单调增)子序列。较为常见的DP问题,实际上就是求以求Ai开始的最长子序列(1<=i<=n)。但关键是分解子问题,求原问题的最优解转化为求子问题的最优解,然后描述子问题,根据子问题的描述寻求最优解的递推关系,然后计算最优解,最终重构原问题的最优解。
关于LIS问题,一般化的子问题可以描述为:集合Aij(i<=j),以Ai开始的LIS(i,j)是从Ai+1开始的第一个大于Ai的元素Ak(i<k<=j)开始的集合的LIS(k,j)+1,应满足条件存在k(i<k<=j)使得Ak>Ai,只要满足此条件,则LIS(k,j)也是最优解,可以采用cut-and-paste以及反证法证明(设LIS(i,j)=l,则满足条件存在k(i<k<=j)使得Ak>Ai的LIS(k,j)=l-1,如果LIS(k,j)不是最优解,即存在m(i<m<=j)使得Am>Ai,有LIS(m,j)>l-1,从而替换掉原来A(i,j)中的部分解使得LIS(i,j)>l,与LIS(i,j)是最优解矛盾);否则LIS(i,j)=1。个人认为这种描述尽管忠于原问题的描述,但在实现时稍稍复杂。因此,可以考虑如下的描述。
LIS问题实际上是一个特殊的计数问题,即对于集合Aij,对于Am(i=<m<=j),统计Ak(m+1<=k<=j)可以在LIS序列中作为Am(i=<m<=j)的后继的计数问题,LIS(i,j)问题即是求这些计数中的最大值。子问题的求解递推关系d(k)=max{d(k), d(m)+1}(Ak>Am)<这个问题可以这样来考虑,假如LIS(i,m)是Aim集合的LIS,那么只要存在Ak>Am(m+1<=k<=j)就有可能使原以Am结尾现以Ak结尾的上升子序列的长度增加1(d(k)=max{d(k), d(m)+1});对Am(i=<m<=j)依次统计便求得分别以Am结尾的上升子序列的长度,因此LIS问题即是求max{d(k)}。>;为方便回溯求解LIS序列,可采用向量pre来记录Ak的前驱。算法描述如下:
1. for i =1 to n
2. d(i) = 1 {每个元素本身可以看作长度为1的子序列
3. for i = 1 to n
4. pre(i) = -1 {-1表示a(i)没有前驱}
5. for i = 1 to n
6. for j = i+1 to n
7. if a(j)>a(i) then {a(j)可以作为a(i)的后继
8. if d(i)+1 > d(j) then
9. d(j) = d(i)+1 {可以作为后继则计数+1,这里就体现了递推关系}
10. pre[j] = i {j的前驱是i}
11. end if
12. end if
13. end for
14. end for
15. for i = 1 to n
16. max = max{d(i)}
17. end for
18. return max
算法的时间复杂性为O(n^2),空间复杂性为O(n)。
关于子问题的描述还有另一种描述方法,对于集合Aij(i<=j),LIS问题可转化为,对于Am(i<=m<=j),统计Ak(i<=k<=m-1)在LIS序列中可以作为Am的前驱的计数问题,LIS(i,j)问题即是求这些计数中的最大值。
关于更为详细的代码以及另一种子问题(对于集合Aij,对于Am(i=<m<=j),统计Ak(i<=k<=m-1)可以在LIS序列中作为Am的前驱的计数问题,LIS(i,j)问题即是求这些计数中的最大值。)描述可以参考:http://www.csie.ntnu.edu.tw/~u91029/LongestIncreasingSubsequence.html
http://scnuznh.blog.hexun.com/18145559_d.html
另外关于O(nlogn)的算法可以参考:
http://hi.baidu.com/napolengogo/blog/item/5c87b53559a1cf335bb5f596.html