zoukankan      html  css  js  c++  java
  • 最长递增子序列问题—LIS

    问题:给定一组数 a0,a0,....,an-1求该序列的最长递增(递减)序列的长度。

    最长递增子序列长度的求法有O(n^2)和O(nlogn)两种算法.

    1.复杂度为O(n^2)的算法。

         设L[i]表示以a[i]结尾的最长递增子序列的长度。则ans=max{L[1],...,L[n]};当i=1时,显然长度为1,即L[1]=1;L[i]的递归方程如下:

                          L[i]=max{L[j]}+1  (j<i且a[j]<a[i]).

    于是可以采用自底向上来计算L[2]...L[n].

    #define _CRT_SECURE_NO_DEPRECATE
    #include<iostream>
    #include<algorithm>
    using namespace std;
    const int N = 100001;
    int L[N],a[N];
    int ans,n;
    void LIS(){
    	L[0] = 1, ans = 1;
    	int i,j;
    	for (i = 1; i < n; i++){
    		L[i] = 1;
    		for (j = 0; j < i; j++){
    			if (a[j] < a[i] && L[i] < L[j] + 1)
    				L[i] = L[j] + 1;
    		}
    		ans = max(ans, L[i]);
    	}
    }
    int main(){
    	scanf("%d", &n);
    	int i;
    	for (i = 0; i < n; i++)
    		scanf("%d", &a[i]);
    	ans = 0;
    	LIS();
    	//输出以a[i]结尾的最大递增序列长度
    	for (i = 0; i < n; i++)
    		printf("%d
    ", L[i]);
    	//输出整个序列的最大递增序列长度
    	printf("%d
    ", ans);
    	return 0;
    }
    

     2.时间复杂度为O(nlogn)的算法。

    考虑上述动态规划计算L[k]的情况。假设有as,a满足如下条件:

       1)s<t<k

       2) as<at<ak

       3)L[s]=L[t]

    此时将ak加在as或者at的尾部得到的长度相同。但是加在谁后面会更好?显然ak加在as后面会更好。可以这样考虑,如果存在ax满足as<ax<at.这时候将将ak加在as后面显然得到的递增序列会更长。

    定义数组d[i]:L[k]=i+1的所有ak的最小值。即为:d[i]={ak|L[k]=i+1};通俗的讲就是表示最大递增子序列长度为i+1,该序列的最后一个数的最小值。

    d[i]有如下性质:

          1)d[i]的值在整个计算过程中是单调非递增的

          2) 序列d[0]..d[n-1]是严格递增的。d[0]<d[1]<...<d[n-1].

    由于性质2),所以可以使用二分查找的办法,将总的复杂度优化为O(nlogn).算法步骤如下:

          (1)初始化ans=1,d[0]=a[0],i=0

          (2)若i>=n,则算法结束,否则转(3)

          (3)  if   a[i]>d[ans-1]

                       then  d[ans]=a[i];ans=ans+1;

                else

                       ind=Bsearch(d,ans,a[i]),d[ind]=a[i];

          (4)i=i+1,转(2)

    在具体实现时,函数Bserach是二分查找函数,功能是找到a[i]插入的位置。函数返回第一个大于或等于a[i]的值的下标,也即是a[i]要插入的地方。具体做时,可以使用C++库函数lower_bound(),该函数包含在头文件#include<algorithm>中,函数lower_bound()在first和last中的前闭后开区间进行二分查找,返回大于或等于val的第一个元素位置。如果所有元素都小于val,则返回last的位置。需要注意的是,他作用的序列必须是有序的。例如一个数组number序列为:4,10,11,30,69,70,96,100.设要插入数字3,9,111.pos为要插入的位置的下标

    pos = lower_bound( number, number + 8, 3) - number,pos = 0.即number数组的下标为0的位置。

    pos = lower_bound( number, number + 8, 9) - number, pos = 1,即number数组的下标为1的位置(即10所在的位置)。

    pos = lower_bound( number, number + 8, 111) - number, pos = 8,即number数组的下标为8的位置(但下标上限为7,所以返回最后一个元素的下一个元素)。

    #define _CRT_SECURE_NO_DEPRECATE
    #include<iostream>
    #include<algorithm>
    #include<cstdio>
    #include<cstring>
    #include<cmath>
    #include<string>
    #include<map>
    #include<queue>
    #include<vector>
    using namespace std;
    const int N = 1000001;
    int d[N], a[N];
    int f[N];//f[i]用来记录以a[i]结尾的最长递增序列长度
    int ans,n;   //ans记录整个数组的最大递增序列的长度
    void LIS(){
    	d[0] = a[0], ans = 1,f[0] = 1;
    	for (int i = 1; i <n; i++){
    		int pos =lower_bound(d, d + ans, a[i])-d;   //计算a[i]插入的下标
    		d[pos] = a[i];
    		if (pos == ans)
    			ans++;
    		f[i] = pos + 1;  //a[i]插入的下标就是pos处,所以a[i]前面必有pos个数的递增子序列
    	}
    }
    int main(){
    	int T;
    	scanf("%d", &T);
    	while (T--){
    		scanf("%d", &n);
    		for (int i = 0; i < n; i++)
    			scanf("%d", &a[i]);
    		ans = 0;
    		LIS();
    		for (int i = 0; i < n; i++)
    			cout << f[i] << (i == n - 1 ? "
    ":" ");
    	}
    	return 0;
    }
    

      

  • 相关阅读:
    【HTML】添加网页背景音乐
    无线安全之破解WPA/WPA2 加密WiFi
    基于deepin-wine的windows软件打包deb安装包教程
    deepin V20 启用Nvidia驱动方法
    [Liunx]Linux安装screenfetch
    开往-友链接力
    linux常用命令(六)提权和文件上传下载的操作
    抓住会员!奇点云DataNuza重大发布
    喜讯 | 奇点云入选「GMIC 2020 PRO 十佳新生代」榜单
    数据智能应用最终实现企业降本增效
  • 原文地址:https://www.cnblogs.com/td15980891505/p/5700618.html
Copyright © 2011-2022 走看看