最长递增子序列的长度求解并打印(动态规划)

设b[i]是在a[i]为单调递增子序列最后一个元素时,所得最长递增子序列的长度为:
在这里插入图片描述
设计思路1(O(n^2)):
1.a数组存储原始数据
2.b数组存储对应最长上升子序列长度
(比如i=3时,a[i]=3,在i<3中寻找比3小的元素,有1,则b[3]=1+1=2,比如i=5时,a[i]=9,在i<5中寻找比9小的元素,有1,3,5,7比较得到这些满足条件中的b[i]最大值,为3,则b[5]=3+1=4)
在这里插入图片描述
使用pre数组存储最长上升子序列当前元素的上一个元素的下标
在这里插入图片描述

#include<stdio.h>
#include<string.h>
int b[100]={0};
int pre[100]={0};
int c[100]={0};
int max1=0;
int min1=0;

//
void LIS(int *arr,int len)
{
	int maxlen=0;
	int max=0;
	int flag=0;
	for(int i=0;i<=len;i++)
	{
		b[i]=1;
	}
	for(int i=2;i<=len;i++)
	{		
		maxlen=0;
		for(int j=i-1;j>=1;j--)
		{
			if(arr[j]<arr[i] && maxlen<b[j])
			{
				maxlen=b[j];
				flag=j;
			}
		}
		b[i]=maxlen+1;
		pre[i]=flag;
	}
	for(int i=1;i<=len;i++)
	{
		printf("%d ",pre[i]);
	}
	printf("\n");
	for(int i=0;i<=len;i++)
	{
		if(b[i]>=max)
		{
			max=b[i];
		}
	}
	printf("%d\n",max);
}

void LisPrint(int *arr,int len)
{
	int flag=0;
	int max2=0;
	for(int i=len;i>=1;i--)
	{
		if(b[i]>=max1)
		{
			max1=b[i];
			flag=i;
		}
	}
	max2=max1;
	while(b[flag]!=1)
	{
		c[--max1]=arr[flag];
		flag=pre[flag];
	}
	c[--max1]=arr[flag];
	for(int i=0;i<max2;i++)
	{
		printf("%d ",c[i]);
	}
	printf("\n");
}
int main()
{
	int n;
	scanf("%d",&n);
	int arr[n+1]={0};
	for(int i=1;i<=n;i++)
	{
	    scanf("%d",&arr[i]);	
	}
	LIS(arr,n);
	LisPrint(arr,n);
}

运行结果:
在这里插入图片描述
设计思路2(O(nlogn)):

假设存在一个序列d[1…9] = 2 1 5 3 6 4 8 9 7,可以看出来它的LIS长度为5。

我们定义一个序列B,然后令 i = 1 to 9 逐个考察这个序列。
此外,我们用一个变量Len来记录现在最长算到多少了

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

然后,把d[2]有序地放到B里,令B[1] = 1,就是说长度为1的LIS的最小末尾是1,d[1]=2已经没用了,很容易理解吧。这时Len=1

接着,d[3] = 5,d[3]>B[1],所以令B[1+1]=B[2]=d[3]=5,就是说长度为2的LIS的最小末尾是5,很容易理解吧。这时候B[1…2] = 1, 5,Len=2

再来,d[4] = 3,它正好加在1,5之间,放在1的位置显然不合适,因为1小于3,长度为1的LIS最小末尾应该是1,这样很容易推知,长度为2的LIS最小末尾是3,于是可以把5淘汰掉,这时候B[1…2] = 1, 3,Len = 2

继续,d[5] = 6,它在3后面,因为B[2] = 3, 而6在3后面,于是很容易可以推知B[3] = 6, 这时B[1…3] = 1, 3, 6, Len = 3 。

第6个, d[6] = 4,你看它在3和6之间,于是我们就可以把6替换掉,得到B[3] = 4。B[1…3] = 1, 3, 4, Len继续等于3

第7个, d[7] = 8,比4大,B[4] = 8。Len变成4了

第8个, d[8] = 9,得到B[5] = 9,嗯。Len继续增大,到5了。

最后一个, d[9] = 7,它在B[3] = 4和B[4] = 8之间,所以我们知道,最新的B[4] =7,B[1…5] = 1, 3, 4, 7, 9,Len = 5。

所以LIS的长度为5。
需要特别注意的是:
最终得到的B数组中的1,3,4,7,9不是LIS,它只是存储的 对应长度LIS的最小末尾。有了这个末尾,我们就可以一个一个地插入数据。虽然最后一个d[9] = 7更新进去对于这组数据没有什么意义,但是如果后面再出现两个数字 8 和 9,那么就可以把8更新到d[5], 9更新到d[6],得出LIS的长度为6。

#include<stdio.h>
#include<stdlib.h>
#define N 100
int MinEnd[N]={0};
int pre[N]={0};
int index[N]={0};
int result[N]={0};

int BSFirstLarge(int left,int right,int key)
{
	while(left<=right)
	{
		int mid=(left+right)/2;
		if(MinEnd[mid]<=key)
		{
			left=mid+1;
		}
		else
		{
			right=mid-1;
		}
	}
	return left;
}

void LIS(int *arr,int len)
{
	int pos=0;
	int end=0;
	for(int i=0;i<len;i++)
	{
		pos=BSFirstLarge(1,end,arr[i]);
		MinEnd[pos]=arr[i];
		if(pos>end) end++;
	}
	printf("%d\n",end);
}

void LisPrint(int *arr,int len)
{
	int pos=0;
	int end=0;
    index[0]=-1;//表示终止标志
	for(int i=0;i<len;i++)
	{
		pos=BSFirstLarge(1,end,arr[i]);
		MinEnd[pos]=arr[i];
		index[pos]=i;
		pre[i]=index[pos-1];
		if(pos>end) end++;
	}
	int num=end;
    for(int i=index[end];i!=-1;i=pre[i])
    {
    	result[--num]=arr[i];
    }
    for(int i=0;i<end;i++)
    {
    	printf("%d ",result[i]);
    }
    printf("\n");
	
}

int main()
{
    int n;
    scanf("%d",&n);
    int arr[n]={0};
    for(int i=0;i<n;i++)
    {
    	scanf("%d",&arr[i]);
    }
    // LIS(arr,n);
    LisPrint(arr,n);
}

参考博文:
https://www.felix021.com/blog/read.php?1587

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值