最长上升子序列的两种解法(包括了fill函数与lower_bound函数的使用)

题目链接:戳这里

解法一:DP,时间复杂度为O(n^2)

这道题的转移方程非常非常简单,一目了然,先准备一个数组dp

dp[i]=1;代表以dp[i]结尾的最长递增子序列,显然初始值都为1

 

ps:由于memset只能将数组的值整体初始化为0或者-1,那么初始化为别的值可以用fill函数代替

函数格式:fill(first,last,val)   //first 为容器的首迭代器,last为容器的末迭代器,val为将要替换的值。

示例:

int a[200];
fill(a, a+100, 1);  //将数组a的前100个元素初始化为1


,接着从a[1]开始搜到i的最长上升子序列。 (图中数组下标从1开始)

dp[2]呢?

如图,它显然比a[1]高,在执行如下语句时

for(j=1;j<i;j++)   if(a[i]>a[j])

j小于i,也就是2,目前符合条件的只有a[1]a[1]又通过了判断语句,它确实小于a[i],执行下一条语句:

dp[i]=max(dp[i],dp[j]+1);   //状态转换方程

很显然:b[2]显然原来是1,当它和b[1]+1比时,1当然比2小,所以,b[2]自然就是2了。

代码:

#include<iostream>
#include<algorithm>
using namespace std;
int a[1005], dp[1005] = {0};  //dp数组代表以dp[i]结尾的最长递增子序列
int main()
{
	int n;
	cin >> n;
	for (int i = 0; i < n; i++)
	{
		cin >> a[i];
	}
	dp[0] = 1;  //初始化
	for (int i = 1; i < n; i++)
	{
		dp[i] = 1;
		for (int j = 0; j < i; j++)  //寻找dp[i]的最大值
		{
			if (a[i] > a[j])
			{
				dp[i] = max(dp[i], dp[j] + 1);
			}
		}
	}
	int longest = dp[0];
	for (int i = 1; i < n; i++)
	{
		if (dp[i] > longest)longest = dp[i];
	}
	cout << longest << endl;
	return 0;
}

 

解法二:贪心+二分,时间复杂度O(nlogn)

我们可以模拟一个stack

在有大量数据的情况下,这算法效率极高

但是,怎么来优化程序呢?

我们可以这样来模拟:

每输入一个数,如果这个数大于栈顶的那个数,于是把它推入栈中。

 

但是,如果这个数大于栈顶呢,这不证明它不可以更新栈中的

某个元素,这时,就可以运用二分查找了。

 有人可能会问:这个序列是无序的啊。没错,但查找的是stack里面的元素,而这个栈里的所有元素,都是严格递增的,所以,用二分查找可以把问题缩减为O(nlogn)

有些不符合逻辑,不是吗?15的下标比171820都大,为什么能插入呢?但是如果仔细想一想,这好像并不影响正常答案,但如果要输出最长上升子序列,那就要改一改这个算法了。


 

由此,这个查找算法才得以下降到logn,于是,整体也就是O(nlogn)

具体操作如下:

每次取栈顶元素和读到的元素做比较,如果大于,则将它入栈;如果小于,则二分查找栈中的比它大的第1个数,并替换它。最长序列长度即为最后模拟的大小。

这也是很好理解的,对于ij,如果i <ja[i] < a[j],a[i]替换a[j],长度虽然没有改变但a'潜力'增大了。

二分查找算法可以用lower_bound函数代替

lower_bound函数的简单解释:

比如说有一个数组a[5]
            1  3  5  7  18  --- 数值
            0  1  2  3  4  --- 下标

            int pos = lower_bound( a,a+5,2)-a;
        则说明返回的是 1 ,因为3的下标为1
            int pos = lower_bound( a,a+5,6)-a;
        则说明返回的是 3,因为7的下标为3
            int pos = lower_bound( a,a+5,20)-a;
        则说明返回的是 5,因为在这个数组中的所有数字都比20小

 

代码:

#include<iostream>
#include<algorithm>
using namespace std;
int a[1005],stack[1005];  //stack数组模拟栈
int main()
{
	int n;
	cin >> n;
	for (int i = 0; i < n; i++)
	{
		cin >> a[i];
	}

	stack[0] = a[0];
	int longest = 1;  //最长上升序列长度,同时也作为栈顶“指针”
	for (int i = 1; i < n; i++)
	{
		if (a[i] > stack[longest - 1] ) //大于栈顶元素,则入栈
		{
			stack[longest++] = a[i];
		}
		else
		{
			int replace = lower_bound(stack, stack + longest, a[i])-stack;  //代替二分查找,找出第一个大于a[i]的值的下标
			stack[replace] = a[i];   //替换
			
		}
	}
	cout << longest << endl;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值