关于最长递增子序列(LIS)的优化问题

 最长递增子序列:

        即给一个长度为N的数组,求得其中的递增的子序列的最大长度,具体问题为:

给定一个长度为 N 的数列,求数值严格单调递增的子序列的长度最长是多少。

输入格式
第一行包含整数 N。

第二行包含 N 个整数,表示完整序列。

输出格式
输出一个整数,表示最大长度

输入样例:

7

3 1 2 1 8 5 6

输出样例:

4

        对于N的规模较小的时候(<1000)时我们当然可以利用动态规划的来实现,代码如下:

#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1010;
int a[N];
//dp[i]即为以a[i]为尾数的最长递增子序列的数量
int dp[N];
int n;

int main() {
	int ans = 0;
	cin >> n;
	for (int i = 1; i <= n; i++)cin >> a[i];

	for (int i = 1; i <= n; i++) {
		//每个数字都有自己本身的长度为1
		dp[i] = 1;

		//枚举在这之前的所有数
		for (int j = 1; j < i; j++) {
			//接着获取到以该数为尾数的这个子序列的长度,再+1即为当前dp[i]
			if (a[j] < a[i])dp[i] = max(dp[i], dp[j] + 1);//当然得取他们的最大值
		}
		//最后获取以这些数字为尾数的递增子序列中最长的一个就是答案
		if (dp[i] > ans)ans = dp[i];
	}
	cout << ans << endl;
	return 0;
}

不难发现当前算法的复杂度为O(n^2),当N达到足够大时,该程序就会超时。

算法优化:

        其实在分析该算法时不难发现,该算法在进行第二层循环时,是有冗余判断的,假设:

以4为尾数的最长子序列长度为1,(我们简单将其记作dp[4]=1,接下来同理),dp[4] = dp[5] =dp[6] = 1,那么当我判断dp[i]的时候,其实我只需要将i与4进行即可,因为如果i<4,那么一定会小于5和6,接下来就不必判断;如果i>4,因为4,5,6,的dp相同我也不需要去判断i与5,6的大小。

        由此可得出优化的思路,可以将dp相同的尾数的最小值存储到一个数组里,这样当i再与其进行判断时,就无需进行如同上文一样的冗余判断。至此我们可以将算法的优化转换为存储dp相同且尾数最小的数组的实现

数组的实现与分析:

        我们可以创建一个数组q[N],其下标为dp的值,也就是递增子序列的长度,其对应的值也就是,当前长度下对应的最小尾数,我们可以发现该数组的值是递增的,证明如下:

        假设存在q[i]<q[j],i>j;既然q[i]<q[j],则q[i]可以作为尾数为q[j]的子序列的前一个子序列,而q[i]的子序列的长度为i,所以j = i + 1 > i,与前式矛盾,证毕。

        巧合的是,就最开始的代码而言,我们就是通过找到比a[j]要大的a[i]进行dp长度的增加的,所以我们在增加长度的同时也可以实现对数组的维护(也就是始终保证数组的值是相同长度下最小的),实现代码如下:

#include<iostream>
#include<algorithm>
using namespace std;
const int N = 100010;
int a[N], q[N];
int n;

int main() {
	cin >> n;
	for (int i = 0; i < n; i++)cin >> a[i];
	
	int len = 0;//记录q的长度,也就是记录递增子序列的长度
	for (int i = 0; i < n; i++) {
		//遍历a数组时,对于每一个数,要找到一个小于他的最大值,小于是因为我们要找递增子序列,最大值是因为我们要保证该子序列是最长的
		int l = 0, r = len;
		//二分法找a[i]对应的上述分析的位置
		while (l < r) {
			int mid = l + r + 1 >> 1;
			if (q[mid] < a[i])l = mid;
			else r = mid - 1;
		}
		//如果a[i]比q[len]里所有数都要大,r势必会指向q的末尾也就是len,此时就会更新len的长度
		len = max(len, r + 1);
		//维护q数组,如果a[i]是一个相同长度但尾数比q[i]小的数字的话,经过二分,会查找到q[i]的前一个位置:因为相同长度所以一定比q[i-1]大。然后通过r+1找到q[i],永尾数更小的a[i]更新q[i];
		q[r + 1] = a[i];
	}
	//最后得到的长度即为答案
	cout << len << endl;
	return 0;
}

      此时时间复杂度就转变为一层遍历循环(n)加一个二分(logn)O(n\log n)  .

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值