最长递增子序列 (Longest Increasing Subsequence, LIS),POJ 2533, POJ 1631

一、问题描述

设L=<a1,a2,…,an>是n个不同的实数的序列,L的递增子序列是这样一个子序列S=<ak1,ak2,…,akm>,其中k1<k2<…<km且ak1<ak2<…<akm。求最大的m值。比如序列(1, 7, 3, 5, 9, 4, 8)的递增子序列包括(1, 7), (3, 4, 8)等等,最长递增子序列为(1, 3, 5, 8),长度为4。

二、解题思路

(1) 把a1,a2,…,an排序,假设得到a’1,a’2,…,a’n,然后求a与a’的最长公共子序列,这样总的时间复杂度为o(nlg(n))+o(n^2)=o(n^2);

(2) 动态规划的思路:O(n^2) (POJ 2533)

//动态规划的思路:
// 另设一辅助数组b,定义b[n]表示以a[n]结尾的最长递增子序列的长度,
// 则状态转移方程如下:b[k]=max(b[j] && a[j]<a[k],j<k)+1;
// 这个状态转移方程解释如下:在a[k]前面找到满足a[j]<a[k]的最大b[j],然后把a[k]接在它的后面,
// 可得到a[k]的最长递增子序列的长度,或者a[k]前面没有比它小的a[j],那么这时a[k]自成一序列,长度为1.
// 最后整个数列的最长递增子序列即为max(b[k], 0<=k<=n-1);

#include<stdio.h>

int main()
{
	int i,j,n,max;
	int a[1000],b[1000];
	while(scanf("%d",&n)!=EOF)
	{
		for(i=0;i<n;i++)
		{
			scanf("%d",&a[i]);
		}

		b[0]=1;
		max=1;
		for(i=1;i<n;i++)
		{
			b[i]=1;
			for(j=0;j<i;j++)
			{
				if(a[i]>a[j]&&b[j]+1>b[i]) b[i]=b[j]+1;
			}
			if(b[i]>max) max=b[i];
		}
		printf("%d\n",max);
	}
	return 0;
}

(3)动态规划的思路:O(nlogn) (POJ 1631)

#include<stdio.h>

//二分查找
int BSearch(int c[],int len,int k)
{
	int low=0,high=len,mid=len/2;
	while(low<=high)
	{
		if(k>c[mid])low=mid+1;
		else if(k<c[mid])high=mid-1;
		else return mid;
		mid=(low+high)/2;
	}
	return low;
}

//用一个变量len记录到目前为止所找出来的最长递增序列的长度。
//另外准备一个数组c[],用这个数组表示长度为j的递增序列中最后一个元素的值。
//在这里长度为j的递增序列不止一个,我们所要保存是那个最小的。
//为什么呢?因为最后一个元素越小,那么这个递增序列在往后被延长的机会越大。
//初始化c[0] = -1;len = 0;从第一个元素a[1]开始进行二分查找。
//当在c数组里面找到一个数比a[i]小,并且他的后面的数大于或等于a[i]则跳出。
//将a[i]添加到这个数的后面。输出low。
int main()
{
	int i,j,n,max,test;
	int a[40000],c[40000];

	scanf("%d",&test);
	while(test)
	{
		test--;
		scanf("%d",&n);
		for(i=0;i<n;i++)
		{
			scanf("%d",&a[i]);
		}

		max=1;
		c[0]=-1;
		c[1]=a[0];
		for(i=1;i<n;i++)
		{
			j=BSearch(c,max,a[i]); //c[j-1]<a[i]<=c[j]
			c[j]=a[i];             // |       |    |        
			if(j>max)max=j;        //high   a[i]  low     
		}                      
		printf("%d\n",max);
	}
	return 0;
}

 

点赞