最长上升子序列(优化)

描述

一个数的序列bi,当b1 < b2 < … < bS的时候,我们称这个序列是上升的。对于给定的一个序列(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

先写一下dp普通算法

思路

f[i]以第i个数结尾的最长上升子序列的长度
每一个上升子序列包括自己,将每一个值初始化为1,之后遍历之前的值,如果该值大于之前的值,则比较是现在的值大还是之前某个值加1大。最后答案不是为f数组的最后一个数,因为最长上升子序列不一定以最后一个数为结尾。
转移方程
if(w[i]>w[j])
f[i]=max(f[j]+1,f[i])

代码

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

int w[1001];
int f[1001];

int main()
{
	int n,ans=0;
	cin>>n;
	for(int i=1;i<=n;i++)
		scanf("%d",&w[i]);
	for(int i=1;i<=n;i++)
	{
		f[i]=1;
		for(int j=1;j<i;j++)
		{
			if(w[i]>w[j])
				f[i]=max(f[j]+1,f[i]);
		}
		ans=max(ans,f[i]);
	}
	cout<<ans;
	return 0;
}

优化

普通算法的时间复杂度为O(n^2),如果数据容量过大,肯定会超时的。
定义一个辅助数组,用于存储最长上升子序列,且其中的元素均达到能达到的最小状态。先把f[1]设为w[1],之后遍历w数组的每个元素,判断它是否大于f数组的尾元素,如果大于,则把它也加入到f数组之中;如果不大于,则从头遍历f数组,如果该数小于其中一个数,将其替换,因为f数组中的数越小越好,其越小,后面的数才容易进入。为什么可以替换呢?比如f数组原来是1 5 7 8,需要判断的数为4,4大于1,不能替换,继续,4小于5,可以替换。需要替换的数肯定小于被替换的数,大于被替换的数的前一个数,替换后保证原数组还是上升的。这样就能做到每一个数都尽可能得小,是一种贪心的算法。最后的答案就为f数组的长度
注意
替换的判断条件是小于等于,如果小于的话,碰见一个相等的数,在此处没有替换,它肯定会比下一个数小,然后把下一个数替换掉,从而导致了序列中有了两个相等的数,影响后面所有的判断。
另外,替换掉一个记得退出循环

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

int w[100001];
int f[100001];

int main()
{
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)
		scanf("%d",&w[i]);
	f[1]=w[1];
	int len=1;							//f数组的长度 
	for(int i=2;i<=n;i++)
	{ 
		if(w[i]>f[len])					//大于末尾的数,则加入 
 			f[++len]=w[i];						
		else
			for(int j=1;j<=len;j++)
				if(w[i]<=f[j])			//等号注意!!!一样也要替换掉,如果不换,它就会换掉后面一个,从而使得这个数与后面那个相等
				{
					f[j]=w[i];			//小于前面的数,则替换 
					break;				//!!!替换完,别忘退出循环 
				}
	} 
	cout<<len;
	return 0;
}

这么算在查找第一个小于的数时,可能还会消耗大量时间,可以使用二分查找优化。这样优化后时间复杂度为O(nlogn)

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

int w[100001];
int f[100001];

int main()
{
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)
		scanf("%d",&w[i]);
	f[1]=w[1];
	int len=1;							//f数组的长度 
	for(int i=2;i<=n;i++)
	{ 
		if(w[i]>f[len])					//大于末尾的数,则加入 
 			f[++len]=w[i];						
		else
		{
			int l=1,r=len,mid,t=len;
			while(l<=r)
			{
				mid=l+(r-l)/2;
				if(w[i]<=f[mid])		//注意等号
				{
					t=mid;				//二分替换 
					r=mid-1;
				}
				else
					l=mid+1;
			}
			f[t]=w[i];
		}
	} 
	cout<<len;
	return 0;
}

另外,二分查找可以不用写,直接调用lower_bound()函数也可
点击查看 lower_bound( )和upper_bound( )的用法

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

int w[100001];
int f[100001];

int main()
{
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)
		scanf("%d",&w[i]);
	f[1]=w[1];
	int len=1;							//f数组的长度 
	for(int i=2;i<=n;i++)
	{ 
		if(w[i]>f[len])					//大于末尾的数,则加入 
 			f[++len]=w[i];						
		else
		{
			int p=lower_bound(f+1,f+len+1,w[i])-f;	//注意此处是lower  不是upper
			f[p]=w[i];					//替换 
		}
	} 
	cout<<len;
}
  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: 最长上升子序列(Longest Increasing Subsequence, LIS)是指一个序列中最长的严格递增子序列的长度。以下是 C++ 实现 LIS 的示例代码: ```c++ #include <iostream> #include <algorithm> #include <vector> using namespace std; int main() { vector<int> nums = {10, 9, 2, 5, 3, 7, 101, 18}; vector<int> dp(nums.size(), 1); // dp[i] 表示以 nums[i] 结尾的最长上升子序列长度 int res = 1; // 记录最大长度 for (int i = 1; i < nums.size(); i++) { for (int j = 0; j < i; j++) { if (nums[j] < nums[i]) { dp[i] = max(dp[i], dp[j] + 1); // 状态转移方程 } } res = max(res, dp[i]); } cout << res << endl; // 输出最长上升子序列长度 return 0; } ``` 该代码的时间复杂度为 O(n^2),可以通过二分查找优化到 O(nlogn)。 ### 回答2: 最长上升子序列(Longest Increasing Subsequence)是指在一个给定序列中,找出一个最长的子序列使得子序列中的元素按照顺序递增。给定一个长度为n的序列A,最长上升子序列的长度可以通过动态规划的方法求解。 假设dp[i]表示以第i个元素结尾的最长上升子序列的长度,那么状态转移方程可以定义为: dp[i] = max(dp[j] + 1), 其中0 <= j < i,A[j] < A[i] 首先,初始化dp组为1,表示每个元素本身就构成一个长度为1的最长上升子序列。 然后,从左往右遍历组A,对于每个元素A[i],遍历之前的所有元素A[j](j<i),如果A[j] < A[i],则更新dp[i]为dp[j]+1。 最后,返回dp组中的最大值即为最长上升子序列的长度。 举个例子,给定序列A=[3, 10, 2, 1, 20],首先初始化dp组为[1, 1, 1, 1, 1]。 遍历到元素10时,与3比较,满足条件A[j] < A[i],更新dp[1]为dp[0]+1,得到dp=[1, 2, 1, 1, 1]。 再遍历到元素2时,与3和10比较,均不满足条件,不更新dp组,得到dp=[1, 2, 1, 1, 1]。 继续遍历到元素1时,与3、10和2比较,满足条件A[j] < A[i],更新dp[3]为dp[2]+1,得到dp=[1, 2, 1, 2, 1]。 最后遍历到元素20时,与3、10、2和1比较,均满足条件,更新dp[4]为dp[3]+1,得到dp=[1, 2, 1, 2, 3]。 返回dp组中的最大值3,即为最长上升子序列的长度。 综上所述,利用动态规划可以求解最长上升子序列的长度。 ### 回答3: 最长上升子序列(Longest Increasing Subsequence,LIS)是指在一个给定序列中,找到一个最长的子序列,使得这个子序列中的元素按照严格递增的顺序排列。 设序列为a[1…n],定义dp[i]为以a[i]结尾的LIS的长度。那么转移方程可以表示为:dp[i] = max{dp[j] + 1 | 1 ≤ j < i, a[j] < a[i]}。 根据状态转移方程,我们需要遍历所有小于i的j,找到能够构成最长上升子序列的j,从而更新dp[i]。可以使用动态规划的思路,通过一个辅助组dp来记录每个位置的最长上升子序列长度。 具体实现上,我们可以使用两个循环来遍历序列a,外层循环从1到n,内层循环从1到i。在内层循环中,比较a[j]和a[i]的大小,若满足条件则更新dp[i]为较大值。 最后,我们只需要遍历dp组中的最大值,即为最长上升子序列的长度。 例如,对于序列c = [1, 3, 5, 2, 4, 6, 7],通过使用动态规划的方法,我们可以得到dp = [1, 2, 3, 2, 3, 4, 5]。最长上升子序列的长度为5。 最长上升子序列问题是一个经典的动态规划问题,它的时间复杂度为O(n^2)。同时,还存在更优化的解法,比如使用二分查找加速查找过程,将时间复杂度优化为O(nlogn)。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值