最长递增子序列(LIS)

300. Longest Increasing Subsequence(good!)


给定一个长度为n的数组,找出一个最长的单调递增子序列(不一定连续,当时先后顺序不能乱)。 更正式的定义是:

设L=<a1,a2,…,an>是n个不同的实数的序列,L的递增子序列是这样一个子序列Lin=<ak1,ak2,…,akm>,其中k1<k2<…<km且ak1<ak2<…<akm。求最大的m值

比如数组A 为{10, 11, 12, 13, 1, 2, 3, 15}, 那么最长递增子序列为{10,11,12,13,15}。

以i结尾的序列的最长递增子序列和其[0, i - 1]“前缀”的最长递增子序列有关,设LIS[i]保存以i结尾的最长递增子序列的长度
    若i = 0,则LIS[i] = 1;
    若i > 0,则LIS[i]的值和其[0, i - 1]前缀的最长递增子序列长度有关,用j遍历[0, i - 1]得到其最长递增子序列为LIS[j],对每一个LIS[j],如果序列array[j]  < array[i]并且LIS[j] + 1 > LIS[i],则LIS[i]的值变成LIS[j] + 1。即:
    LIS[i] = max{1, LIS[j] + 1},其中array[i] > array[j] 且 j = [0, i - 1]。

最开始写的程序貌似是对的

#include<iostream>
using namespace std;
//LIS-longest increasing subsequent
int Lis(int arry[],int len){
	int *lis=new int[len];
	int max=-1;
	for(int i=0;i<len;i++){
		lis[i]=1;
		for(int j=0;j<i;j++){
			if(arry[i]>arry[j]&&(lis[j]+1)>lis[i]){//要满足(lis[j]+1)>lis[i],否则不应该加1
				lis[i]=lis[j]+1;
			}
		}
		if(lis[i]>max)
			max=lis[i];
	}

	//可以再上个循环中操作
/*	int max=-1;
	for(int i=0;i<len;i++){
		if(lis[i]>max)
			max=lis[i];
	}*/
	delete []lis;
	return max;
}
int main(){
	int arry[]={2,1,5 ,3, 6, 4, 8 ,9 ,7};
	int len=9;
	cout<<Lis(arry,len)<<endl;
	system("pause");
	return 0;
}

可以输出相应的最长递增项

#include<iostream>
#include<vector>
using namespace std;
//LIS-longest increasing subsequent
int Lis(int arry[],int len){
	int *lis=new int[len];
	vector<vector<int>>tmp(len);
	int max=-1;
	for(int i=0;i<len;i++){
		lis[i]=1;
		for(int j=0;j<i;j++){
			if(arry[i]>arry[j]&&(lis[j]+1)>lis[i]){//要满足(lis[j]+1)>lis[i],否则不应该加1
				lis[i]=lis[j]+1;
				tmp[i].push_back(arry[j]);
			}
		}
		tmp[i].push_back(arry[i]);
		if(lis[i]>max)
			max=lis[i];
	}
	for(int i=0;i<len;i++){
		if(lis[i]==max){
			for(int j=0;j<tmp[i].size();j++){
				cout<<tmp[i][j]<<" ";
			}
			cout<<endl;
		}
	}
	delete []lis;
	return max;
}
int main(){
	int arry[]={2,1,5 ,3, 6, 4, 8 ,9 ,7};
	int len=9;
	cout<<Lis(arry,len)<<endl;
	system("pause");
	return 0;
}

输出时2 5 6 8 9

再看一个扩展问题


上面的时间复杂度是O(n^2),其实还有O(nlgn)的方法

http://www.ahathinking.com/archives/117.html

编程之美》对于这个方法有提到,不过它的讲解我看得比较难受,好长时间才明白,涉及到的数组也比较多,除了源数据数组,有LIS[i]和MaxV[LIS[i]],后来看了大牛Felix的讲解,我才忽然发现编程之美中的这个数组MaxV[LIS[i]]在记录信息上其实是饶了弯的,因为我们在寻找某一长度子序列所对应的最大元素最小值时,完全没必要通过LIS[i]去定位,即没必要与数据arr[i]挂钩,直接将MaxV[i]的下标作为LIS的长度,来记录最小值就可以了(表达能力太次,囧。。。),一句话,就是不需要LIS[i]这个数组了,只用MaxV[i]即可达到效果,而且原理容易理解,代码表达也比较直观、简单。

下面说说原理:

目的:我们期望在前i个元素中的所有长度为len的递增子序列中找到这样一个序列,它的最大元素比arr[i+1]小,而且长度要尽量的长,如此,我们只需记录len长度的递增子序列中最大元素的最小值就能使得将来的递增子序列尽量地长。

方法:维护一个数组MaxV[i],记录长度为i的递增子序列中最大元素的最小值,并对于数组中的每个元素考察其是哪个子序列的最大元素,二分更新MaxV数组,最终i的值便是最长递增子序列的长度。这个方法真是太巧妙了,妙不可言。

#include <iostream>
#include<cstdio>
using namespace std;

/* 最长递增子序列 LIS
 * 设数组长度不超过 30
 * DP + BinarySearch
*/

int MaxV[30]; /* 存储长度i+1(len)的子序列最大元素的最小值 */
int len;      /* 存储子序列的最大长度 即MaxV当前的下标*/

/* 返回MaxV[i]中刚刚大于x的那个元素的下标 */
int BinSearch(int * MaxV, int size, int x)
{
    int left = 0, right = size-1;
    while(left <= right)
    {
        int mid = (left + right) / 2;
        if(MaxV[mid] <= x)
        {
            left = mid + 1;
        }else
        {
            right = mid - 1;
        }
    }
    return left;
}

int LIS(int * arr, int size)
{
    MaxV[0] = arr[0]; /* 初始化 */
    len = 1;
    for(int i = 1; i < size; ++i) /* 寻找arr[i]属于哪个长度LIS的最大元素 */
    {
        if(arr[i] > MaxV[len-1]) /* 大于最大的自然无需查找,否则二分查其位置 */
        {
            MaxV[len++] = arr[i];
        }else
        {
            int pos = BinSearch(MaxV,len,arr[i]);
            MaxV[pos] = arr[i];
        }
    }
    return len;
}

int main()
{
    int arr[] = {1,-1,2,-3,4,-5,6,-7};

    /* 计算LIS长度 */
    printf("%d\n",LIS(arr,sizeof(arr)/sizeof(int)));
    return 0;
}



1.1从一列数中筛除尽可能少的数使得从左往右看,这些数是从小到大再从大到小的(网易)

双端LIS问题,用动态规划的思想可以解决,目标规划函数为max{B[i] + C[i]},其中B[i]是从左到右的,0~i个数之间满足递增的数字个数;C[i]为从右到左的,n- 1 ~ i个数之间满足递增的数字个数。最后结果为n - max + 1(+1是因为B[i]和C[i]算重一个)
#include<iostream>
#include<vector>
using namespace std;

//返回最少删除的数个数
int DoubleLis(int arry[],int len){
	int max=-1;
	int *B=new int[len];
	int *C=new int[len];

	for(int i=0;i<len;i++){
		B[i]=1;
		C[i]=1;
	}

	for(int i=0;i<len;i++){	
		for(int j=0;j<=i;j++){
			if(arry[i]>arry[j]&&B[i]<(B[j]+1)){
				B[i]=B[j]+1;
			}
		}
	}

	for(int i=len-1;i>=0;i--){
		for(int t=len-1;t>=i;t--){
			if(arry[i]>arry[t]&&C[i]<(C[t]+1)){
				C[i]=C[t]+1;
			}
		}
	}

	for(int i=0;i<len;i++){
		if((B[i]+C[i])>max)
			max=B[i]+C[i];
	}

	delete []B;
	delete []C;
	return len-max+1;
}

int main(){
	int arry[]={1,4,3,5,6,7,2,0};
	int len=8;
	cout<<DoubleLis(arry,len)<<endl;
	
	system("pause");
	return 0;
}
结果是1



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值