当前位置:   article > 正文

最长上升子序列的两种算法(LIS)_最长上升子序列算法

最长上升子序列算法

最长上升子序列就是求:给定的一串数字中 找出不断上升的最长那一串(该串不一定相连,只保证递增即可)。就比如下面这个例子   其最长上升子序列为2 3 4 7或2 3 4 6  数串长度为4 那么具体怎么求的呢

我们拿一个最简单的例子讲:

【题目描述】

给定N个数,求这N个数的最长上升子序列的长度

【样例输入】

7

2 5 3 4 1 7 6

【样例输出】

4

一、朴素算法   (复杂度O(n^2))

dp[i]表示从0~i的以a[i]结尾的最大上升子序列长度,而以a[i]结尾的最长上升子序列有两种:1.只包含a[i]的子序列(即为其自身长度dp[i]=1);  2.在满足j<i且a[j]<a[i]的以a[j]为结尾的上升子序列末尾,追加上a[i]得到的子序列(即dp[i]=a[j]+1)。
所以有如下递推关系:

dp[i]的值为max(dp[j]+1,dp[i])也就是:

i0123456
a[i]2534176
dp[i]1223144

没看懂表格没关系,下面是具体解释 对a[i]从头开始遍历,第一个数是2,由于2前面没有数字,则到2的最大长度就是1(其本身)即dp[0]=1。接着a[1]=5。a[1]>a[0].那么这时候dp[2]=dp[1]+1=2; a[2]=3,往前遍历3<5不符合条件,3>2满足dp[3]=dp[1]+1=2;a[3]=4,往前遍历找满足条件的最大值 4>3,dp[3]=dp[2]+1=3,4<5不符合条件,4>2 dp[1]+1=2<(dp[3]=3),则dp[3]=3。以此类推 。。。   

下面上代码:

  1. #include<stdio.h>
  2. #include<iostream>
  3. #include<algorithm>
  4. using namespace std;
  5. int main(){
  6. int n,dp[100010],a[100010];
  7. int i,j,maxn;
  8. while(~scanf("%d",&n)){
  9. for(i=0;i<n;i++){
  10. scanf("%d",&a[i]);
  11. }
  12. memset(dp,0,sizeof(dp));
  13. for(i=0;i<n;i++){
  14. dp[i]=1;
  15. for(j=i;j>=0;j--){//往前遍历,找到以a[i]结尾的最大值
  16. if(a[i]>a[j]){
  17. dp[i]=max(dp[i],dp[j]+1);
  18. }
  19. }
  20. }
  21. maxn=0;
  22. for(i=0;i<n;i++){//遍历dp[i]找到最大值
  23. if(maxn<dp[i]){
  24. maxn=dp[i];
  25. }
  26. }
  27. printf("%d\n",maxn);
  28. }
  29. return 0;
  30. }

二、二分法(复杂度nlogn)

用dp[i]表示数字串长度i的时对应的最小末尾值。然后令 i = 1 to 9 逐个考察这个序列。

此外,我们用一个变量Len记录目前最长算到多少了

还是以上面的题举例

首先,把a[1]有序地放到dp里,令dp[1] = 2,表示当只有1一个数字2的时候,长度为1的LIS的最小末尾是2。这时的Len = 1。

接着,a[2] = 5,a[2] > dp[1],所以令dp[1+1] = dp[2] = a[3] = 5,表示长度为2的LIS的最小末尾是5,这时候dp[1..2] = 2, 5,这时的Len = 2。

接着,a[3] = 3,它正好夹在了1和
5之间,放在1的位置显然不合适,因为1小于3,长度为1的LIS最小末尾应该是1,这样很容易推知,长度为2的LIS最小末尾是3,于是把5淘汰掉,这时候dp[1..2] = 2, 3,这时Len = 2。


继续,a[4] = 4,它在3后面,因为dp[2] = 3, 而4在3后面,于是很容易可以推知dp[3] = 4, 这时dp[1..3] = 2, 3, 4,这时的Len = 3。

 a[5] = 1,这时候往前遍历你会发现a[5]<dp[1],则这时 将dp[1]替换为a[5]即dp[1]=a[5]=1,此时dp[1..2]=1,3,4

a[6] = 7,它很大,比4大,于是dp[4] = 7,这时的Len = 4。


 a[7] = 6,dp[3]<a[7]<dp[4]得到dp[4] = 6,这时的Len = 4。

注意。这个1,3,4,6不是LIS,它只是存储的对应长度LIS的最小末尾。有了这个末尾,我们就可以一个一个地插入数据。
下面给出例题模版:

用STL写:

  1. #include<stdio.h>
  2. #include<string.h>
  3. #include<algorithm>
  4. using namespace std;
  5. #define INF 0x3f3f3f
  6. int dp[10010];//dp[i]表示长度为i+1的子序列末尾元素最小值;
  7. int a[10010];
  8. int main()
  9. {
  10. int n;
  11. while(scanf("%d",&n)!=EOF)
  12. {
  13. for(int i=0;i<n;i++)
  14. {
  15. scanf("%d",&a[i]);
  16. dp[i]=INF;//不可以用memset对数组赋值INF,只能赋值0或-1;
  17. //可以用fill(dp,dp+n,INF);
  18. }
  19. for(int i=0;i<n;i++)
  20. {
  21. *lower_bound(dp,dp+n,a[i])=a[i];//找到>=a[i]的第一个元素,并用a[i]替换;
  22. }
  23. printf("%d\n",lower_bound(dp,dp+n,INF)-dp);//找到第一个INF的地址减去首地址就是最大子序列的长度;
  24. }
  25. return 0;
  26. }
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/1004930
推荐阅读
相关标签
  

闽ICP备14008679号