最长单调子序列求解



本篇博文为追忆曾经写过的算法系列第三篇

温故知新

题目重述

已知一个序列,由随机数构成,求其最长单调子序列。

要求:单调分严格和不严格两种情况,并分别求解并输出一个最长单调子序列和所有符合要求的子序列。

问题分析

本题是求解有约束条件的子序列问题,可用动态规划求解。由于本题是求解最长单调子序列的,包括求一个最长单调子序列和求解所有符合要求的序列,下面将按照这两种情况讨论算法复杂度。

求解一个最长单调子序列的算法复杂度

本题假设单调为递增的情况,序列长度为N(任意大小),即序列S[N]。若采用直接搜索的方法,同时定义数组LP[N]记录对应序列元素所在最长单调子序列中的位置。其算法思想如下:序列从S[1]开始(S[0])已经初始化),每递增一个,判断与之前的每个数值的大小,若S[i]>
S[j](j<i) (注:若非严格则是“>=”)且LP[i]<=LP
[j]+1,则更新LP[i]为LP[j]+1。这样保证了每个元素归类到自身所满足的最长子序列当中,但此算法的复杂度为O(n^2)。

通过对第二层循环,即确定新加进的元素其所在最长子序列的位置,可改进搜索策略,将复杂度降低为O(nlogn)。基本思想是:定义数组Len[N+1],第0个元素空闲,第j(j>0)个位置存储所有长度为j的子序列中最后元素的最小值,这样可以保证当前序列为最长序列且保持局部最优。当对新添加的元素S[i]进行判别时,采用二分搜索法在Len数组中搜索元素的位置,由于Len一直保持升序排列,且搜索到其所在的位置后,取代比他大的元素,从而成为那个长度的序列最后元素最小,其搜索复杂度为O(logn)。在算上第一层循环的O(n),所以复杂度为O(nlogn)。通过LP[N]和S[N]可循环求解出一个最长单调子序列,复杂度为O(n)。所以,总复杂度为O(nlogn)。

求解所有最长单调子序列的复杂度

求解所有最长单调子序列,其和2.1中的唯一不同在于求解输出所有最长单调子序列,而求解LP[N]和MaxLen的复杂度同2.1中的分析,最好方法的复杂度为O(nlogn)。输出所有最长单调子序列(设其复杂度为O(T))和上述过程是并列而非嵌套,所以总的复杂度为。

而T的求解很容易,通过分析可以发现,其中第一个n指循环LP[N]得到最长子序列的起始位置,而k指所有最长子序列的个数,取小于号是因为最长序列的起始点总是小于n的。

综上可以得到,求解所有最长单调子序列的复杂度为,其中k的取值为[0,n]。即极端情况下为O(n^2),这里之所以讨论是想强调无限长序列下有限个最长单调子序列的思想。

算法思想与实现

a.定义S[N],LP[N],Len[N+1]三个数组,其分别是序列本身,序列元素对应的最长子序列位置记录,长度为i的子序列最末元素的最小值。其中S[N]随机生成,Len[0]闲置;

b.初始化LP[0]为1,S[N]从i=1开始循环至N-1;K用于记录Len的最大值,即目前搜索到的最长子序列长度,其初始值为K=1;若S[i]>Len[K]
(注:若非严格则是“>=”),即下一个元素的值大于最长子序列最后元素的值,则直接将S[i]放在Len[++K]的位置;若S[i]<=Len[K],则进入步骤c;

c.调用函数k
= fn_InsertPos(),并执行如下操作: Len[k]=S[i]; LP[i] = k; LP[i]仍记录了S[i]所在的最长子序列位置,有利于序列的输出;fn_InsertPos()采用二分查找法,但和常规的查找条件不同,其算法复杂度为O(logn);

d.完成S[i]的搜索后,Len所记录的k值即为最长单调子序列的长度,此时可通过LP[N
]数组的记录求出一个最长子序列,即从后向前遍历LP[N],用辅助数组P[N]记录子序列,k记录当前需要存入的子序列长度,当LP[i]==k且S[i]<P[k]时(注:若非严格则是“>=”),将S[i]存入P[--k],直至k==0,具体过程可参见函数void
fn_OutPutLMS(int Pos ),其算法复杂度为;

e.要输出所有最长单调子序列,则需要确定所有最长子序列的末尾元素所在的位置,这个容易实现,即定义数组C[N],遍历LP[N]并记录值为MaxLen的元素的位置。然后一次调用voidfn_OutPutLMS(int
Pos ),复杂度为。

程序实现

/*-----------------------------------------------------------------
*                    最长单调子序列问题
* -----------------------------------------------------------------
*  By Gu Jinjin SEU
*  求解最长单调子序列,分严格和不严格两种情况
*  这里以单调递增为例
*  Time : 2012/11/28-29   Weather:rainy
*/

#include <iostream>
#include <cstdlib>
#include <ctime>

using std::cout;
using std::endl;
// define
#define N 5000
// S[N]-序列,LP[N]-序列元素在最长子序列中的位置
// Len[N+1]-用于记录i长度的所有单调子序列末尾元素最小值
int S[N],LP[N],Len[N+1];
// 记录最长单调子序列长度
int MaxLen; 

// 函数声明
void fn_RandNum();
int fn_InsertPos(int Si, int K);
int fn_GetLMS_Len();
void fn_OutPutInitList();
void fn_OutPutLMS(int Pos);
void fn_GetAllLMSes();

/*-----------------------------------------------------------------
*                void main( void )
* -----------------------------------------------------------------
* 主函数
*/
void main()
{
	clock_t t_start,t_end;
	fn_RandNum();
    t_start=clock();
	MaxLen = fn_GetLMS_Len();
	t_end=clock();
	cout<<"Inital List:"<<endl;
	cout<<"=============================="<<endl;
	fn_OutPutInitList();
	cout<<"All LMSes:"<<endl;
	cout<<"=============================="<<endl;
	fn_GetAllLMSes();
	cout<<"=============================="<<endl;
	cout<<"The needed time:"<<difftime(t_end,t_start)<<"ms"<<endl;
}

/*-----------------------------------------------------------------
*                void fn_RandNum( ... )
* -----------------------------------------------------------------
* 生成随机数
*/
void fn_RandNum()
{
	// 用于保证是随机生成的数
	// 不同的种子可以生成不同的随机数
	//srand((unsigned)time(NULL));
	for(int i=0; i<N; i++)
	{
		S[i] = rand()%N;
		LP[i] = 1; // 数组初始化
		Len[i] = 0;
	}
}

/*-----------------------------------------------------------------
*                void fn_InsertPos( ... )
* -----------------------------------------------------------------
* 计算元素所在的最长子序列中位置,并返回
*/
int fn_InsertPos(int Si, int K)
{
	int low=1, high=K, mid; //定义上下界和中间值
	mid=(low+high)/2;
	// 若low>high,则说明搜索到
	while(low <= high)
	{
		if(low > high)break;
		else if(Len[mid]<Si)low = mid+1;
		else high = mid -1;
		mid=(low+high)/2;
	}
	//返回插入的位置,即S[i]元素所对应的最长子序列的长度
	return(high+1);
}

/*-----------------------------------------------------------------
*                void fn_GetLMS_Len( ... )
* -----------------------------------------------------------------
* 计算LMS的长度
*/

int fn_GetLMS_Len()
{
	int lmn=1,k=1;
	Len[k]=S[0];
	for(int i=1; i<N; i++)
	{
		if(S[i]>Len[lmn])
		{
			Len[++lmn]=S[i];
			LP[i]=lmn;
		}
		else
		{
			k = fn_InsertPos(S[i],lmn);
			Len[k] = S[i];
		    LP[i] = k;
		}
	}
	return(lmn);
}

/*-----------------------------------------------------------------
*                void fn_OutPutInitSeq( ... )
* -----------------------------------------------------------------
* 输出原始数列
*/
void fn_OutPutInitList()
{
	cout<<"S"<<'\t'<<"LP"<<'\t'<<"Len"<<endl;
	cout<<"------------------------------"<<endl;
	for(int i=0; i<N; i++)
	{
		cout<<S[i]<<'\t'<<LP[i]<<'\t'<<Len[i]<<endl;
	}
}

/*-----------------------------------------------------------------
*                void fn_OutPutLMS( ... )
* -----------------------------------------------------------------
* 输出一个LMS函数
*/
void fn_OutPutLMS(int Pos)
{
	int P[N],k=MaxLen-1;
	P[k]=S[Pos];

	for(int i=Pos-1; i>=0; i--)
	{
		if(LP[i] == k && S[i]< P[k])P[--k]=S[i];
	}
	// OutPut LMS
	if(k==0)
	{
		for(int i=0; i<MaxLen; i++)cout<<P[i]<<'\t';
		cout<<endl;
		cout<<"------------------------------"<<endl;
	}
}

/*-----------------------------------------------------------------
*                void fn_GetAllLMSes( ... )
* -----------------------------------------------------------------
* 获取所有LMSes
*/
void fn_GetAllLMSes()
{
	// C[N]用于记录长度为MaxLen序列的元素在LP[N]中位置
	int C[N], k=0;
	for(int i=N-1; i>=MaxLen-1; i--)
	{
		if(LP[i]==MaxLen){ C[k]=i; k++;}
	}

	for(int i=0;i<k;i++)
	{
		fn_OutPutLMS(C[i]);
	}
}

结果(N取20的时候,其中数组为随机函数生成)

最长单调子序列求解

时间: 2024-10-11 22:49:22

最长单调子序列求解的相关文章

最长单调子序列(O(n^2) 和 O(nlg(n))))

给出一个数n,然后给出n个数,求n个数的最长单调子序列.如: n = 7 7    1     5    2    9    3    6 方法一:DP dp[i] 表示元素值不超过i时的最长单调序列.时间复杂度为O(n^2)则可以这样: #include <stdio.h> #include <stdlib.h> #include <string.h> #define N 100000 int a[N]; int dp[N]; int main() { //freop

动态规划--最长单调子序列问题

1.问题描述: 求一个正整数序列的最长单调自增子序列,子序列不要求是连续的.例如 Input:5 5 2 4 3 1 Output:2 2. 算法复杂度是O(N*N) 确定状态转移方程,设f[i]是以a[i]为结尾的最大值的子序列的长度,那么\[\max \{ f[i]\} \]的最大值就是要的结果. 所以转移方程为: \[f(i) = \max \{ f(x)|x < i,{a_i} > {a_x}\}  + 1\] 所以代码可以为: void main(void) { int arr[]

poj 1952 BUY LOW, BUY LOWER[最长单调子序列变形]

题目:poj 1952 BUY LOW, BUY LOWER 题意:给出一个序列,先求最长单调递减子序列,然后求在子序列最长的情况下,不同的长度都为最长的的子序列的个数.(比如3,2,1和3,2,1属于相同,只能算一个) 分析:首先用一个dp[i]表示到当前i点的最长子序列的长度 用dp2[i]表示最长为dp[i]的子序列的个数 然后dp[i] = max(dp[j])+1 (1<=j /************************************ Problem: 1952 Use

HIT2372 Recoup Traveling Expenses(最长单调子序列)

题目链接: http://acm.hit.edu.cn/hoj/problem/view?id=2372 题目描述: Recoup Traveling Expenses Submitted : 206, Accepted : 102 A person wants to travel around some places. The welfare in his company can cover some of the airfare cost. In order to control cost,

LCS求最长公共子序列(DP)

动态规划并不是一种算法,而是一种解决问题的思路.典型的动态规划问题,如最长公共子序列(LCS),最长单调子序列(LIS)等. 动态规划分为四个步骤: 1.判断问题是否具有最优子结构 这里以LCS为例,X={x1,x2,...,xi}:Y={y1,y2,...,yj}.最长公共子序列Z={z1,z2,...,zk}: ①如果xi=yj,那么zk=xi=yj,且Zk-1是序列Xi-1和Yj-1的LCS: ②如果xi≠yj,那么zk≠xi:且Zk是序列Xi-1和Yj的LCS: ③如果xi≠yj,那么z

动规讲解基础讲解七——最长单增子序列

(LIS Longest Increasing Subsequence)给定一个数列,从中删掉任意若干项剩余的序列叫做它的一个子序列,求它的最长的子序列,满足子序列中的元素是单调递增的. 例如给定序列{1,6,3,5,4},答案是3,因为{1,3,4}和{1,3,5}就是长度最长的两个单增子序列. 处看此题,怎么做? 万能的枚举?枚举全部2^n个子序列,找出最长的,固然可以,就是复杂度太高.我们为什么要枚举呢?因为要知道取了哪些数,其实我们只需要考虑上一个数和取了几个数就可以了吧?因为单增的意思

最长单调递减子序列

问题描述:求一个数组的最长递减子序列 比如{9,4,3,2,5,4,3,2}的最长递减子序列为{9,5,4,3,2}. 思路:这是一个标准的动态规划的问题,在不理解算法的时候,最感觉可以使用递归的思想,其实也是正确的,在最后给出一个递归的方法,在知道是动态规划问题以后,就需要进行分析,我们需要一个辅助数组记录信息,假如源数组为src,辅助数组为table,table[i]数组中记录着到src[0]~src[i]这个子数组中包含src[i]构成的最长单调子序列的长度(一定要注意就是table[i]

动态规划5-最长单增子序列

(LIS Longest Increasing Subsequence)给定一个数列,从中删掉任意若干项剩余的序列叫做它的一个子序列,求它的最长的子序列,满足子序列中的元素是单调递增的. 例如给定序列{1,6,3,5,4},答案是3,因为{1,3,4}和{1,5,4}就是长度最长的两个单增子序列. 处看此题,怎么做? 万能的枚举?枚举全部2^n个子序列,找出最长的,固然可以,就是复杂度太高.我们为什么要枚举呢?因为要知道取了哪些数,其实我们只需要考虑上一个数和取了几个数就可以了吧?因为单增的意思

最长单调递增子序列 POJ 3903 Stock Exchange .

题目传送门 : -------->点这里点这里<---------- 题目大意: 给出一串正整数,个数为N个.1<=N<=100000.求最长单调递增子序列长度. 样例输入: 6 5 2 1 4 5 3 3 1 1 1 4 4 3 2 1 样例输出: 3 1 1 =================================================================== 最长单调递增子序列问题的DP朴素算法复杂度为 O(n^2); 然而题目中的 N 最大有