最长上升子序列(LIS)两种解法
Posted k2mno4
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了最长上升子序列(LIS)两种解法相关的知识,希望对你有一定的参考价值。
问题描述 :一个数的序列ai,当a1 < a2 < ... < aS的时候,我们称这个序 列是上升的。
对于给定的一个序列(a1, a2, ..., aN),我们可以得到 一些上升的子序列(ai1, ai2, ..., aiK),这里1 <= i1 < i2 < ... < iK <= N。
比如,对于序列(1, 7, 3, 5, 9, 4, 8),有它的一些上升子 序列,如(1, 7), (3, 4, 8)等等。这些子序列中最长的长度是4,
比 如子序列(1, 3, 5, 8). 你的任务,就是对于给定的序列,求出最长上升子序列的长度。
输入数据
输入的第一行是序列的长度N (1 <= N <= 1000)。第二行给 出序列中的N个整数,这些整数的取值范围都在0到10000。
输出要求
最长上升子序列的长度。
输入样例 7 1 7 3 5 9 4 8
输出样例 4
动态规划法:
状态方程:maxLen[i]=1;(初态)
maxLen[i]=max(maxLen[i],maxLen[j]+1)
Code:
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<stdlib.h>
#include<string.h>
#include<math.h>
using namespace std;
typedef long long LL;
const int maxn=1e5+7;
const int maxm=2e5+7;
const long long mod=1e9+7;
int N,maxLen[maxn],s[maxn];
int main()
{
scanf("%d",&N);
for(int i=1;i<=N;i++)
{
scanf("%d",&s[i]);
maxLen[i]=1;
}
for(int i=2;i<=N;i++)
for(int j=1;j<i;j++)
if(s[i]>s[j])
maxLen[i]=max(maxLen[i],maxLen[j]+1);
printf("%d",*max_element(maxLen+1,maxLen+N+1));
return 0;
}
直接举例:假设这道题的样例是 {1,7,3,5,9,4,8},我们不妨令它分别为 (a1, a2, a3, a4, a5, a6, a7),我们这时候可以假设每一个最长上升的子序列为初始值为maxLen[i]=1,因为我们知道每一个数本身就是一个子序列。
然后我们有a1:1,maxLen[1]=1,而a2=7,maxLen[2]=1,我们这时候比较一下a1、a2,也就是code中的s[i]和s[j],a1<a2,说明maxLen[2]可以比maxLen[1]还长,即maxLen[2]长度为maxLen[1]+1(这个1是指加上a2这个数进入这个子序列,也就是把a2放到a1后面,原来这个暂时最长的上升子序列只是1个元素【a1这个元素】,现在加多了a2),然后i++,此时a3=3,maxLen[3]=1,
因为a3>a1,所以maxLen[3]=maxLen[1]+1(这个1指加上a3进入这个子序列),接着j++,
因为a3<a2,不符合LIS的规律咯,所以maxLen[3]最终=maxLen[1]+1=2,所以a3不能放到a1 a2 后面,也就没有maxLen[2]+1,依此类推就可以找到最长的上升子序列。
有点悲伤的是这种算法的时间复杂度是O(n^2),数据多会容易爆…
二分查找+贪心+动规法:
Code(自己写的lowerbound函数):
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<stdlib.h>
#include<string.h>
#include<math.h>
using namespace std;
typedef long long LL;
const int maxn=1e5+7;
const int maxm=2e5+7;
const long long mod=1e9+7;
int N,arr[maxn],s[maxn],num=1;
int lowerbound(int i)
{
int L=1,R=num,mid,lastPos=0;
while(L<=R)
{
mid=L+(R-L)/2;
if(s[i]>=arr[mid])
R=mid-1;
else
{
lastPos=mid;
L=mid+1;
}
}
return lastPos;
}
int main()
{
scanf("%d",&N);
for(int i=1;i<=N;i++)
scanf("%d",&s[i]);
arr[1]=s[1];
for(int i=2;i<=N;i++)
{
if(arr[num]<s[i])
arr[++num]=s[i];
else
arr[lowerbound(i)]=s[i];
}
printf("%d",num);
return 0;
}
Code(突然发现c++的函数lower_bound之后):
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<stdlib.h>
#include<string.h>
#include<math.h>
using namespace std;
typedef long long LL;
const int maxn=1e5+7;
const int maxm=2e5+7;
const long long mod=1e9+7;
int N,arr[maxn],s[maxn],num=1;
int main()
{
scanf("%d",&N);
for(int i=1;i<=N;i++)
scanf("%d",&s[i]);
arr[1]=s[1];
for(int i=2;i<=N;i++)
{
if(arr[num]<s[i])
arr[++num]=s[i];
else
*(lower_bound(arr+1,arr+num+1,s[i]))=s[i];
}
printf("%d",num);
return 0;
}
依然是要举栗子,假设这个这道题的样例是{5,3,4,1,8},这一眼也能看出最长上升子数列是3,
,我们不妨令它分别为 (a1, a2, a3, a4, a5,),重点它来了它来了!!我们这时候要开一个新的数组arr[ ]存放子数列,原来的数组是s[1]=5,s[2]=3,s[3]=4,s[4]=1,s[5]=8。
一开始我们让num=1,然后将s[1]=5的值赋给arr[1],此时子数列为{5},接着比较s[2]与arr[1]的大小(即s[i]和arr[num]的大小),因为s[2]<arr[1],所以此时子数列变为{3},接着比较arr[1]与s[3],s[3]>arr[1]=3,所以直接将s[3]放在arr[1]的后方,arr[2](arr[++num])=s[3]的值,此时子数列为{3,4},接着arr[2]>s[4],1比3和4都小,但是我们是想要的是保证这个子数列的数尽可能的小一些可以放更多的数进来(贪心),达到最长上升子数列目的,所以我们从arr这个数组中找出第一个比s[4]大的元素,也就是3,将3替换掉,得到当前子数列为{1,4}。因为arr[2]<s[5],直接将8放在arr[3]就完成了这个过程。
直接用动规来解时间复杂度会是O(n^2),但是用了二分查找之后就会变成O(nlogn),数据庞大的时候可想而知是第二种方法更优一些....
为了代码更加的整洁,我把自己在代码的注释给去掉了嘻嘻~
以上是关于最长上升子序列(LIS)两种解法的主要内容,如果未能解决你的问题,请参考以下文章