动态规划 + 二分搜索 | 最长递增子序列 | Longest Increasing Subsequence | C/C++实现

问题描述

求数列 A = a 0 , a 1 , . . . , a n − 1 A=a_0,a_1,...,a_{n-1} A=a0,a1,...,an1的最长递增子序列(LIS)。如果数列A的子序列 a i 0 , a i 1 , . . . , a i k a_{i_0},a_{i_1},...,a_{i_k} ai0,ai1,...,aik满足0 ≤ i 0 i_0 i0 i 1 i_1 i1 ≤ … ≤ i k i_k ik ≤ n且 a i 0 a_{i_0} ai0 a i 1 a_{i_1} ai1 ≤ … ≤ a i k a_{i_k} aik,则称该子序列为数列A的递增子序列。其中k值最大的子序列称为最长递增子序列。

输入:
第1行输入表示数列A长度的整数n。接下来n行输入数列各元素 a i a_i ai
输出:
输出最长递增子序列的长度,占1行
限制:
1 ≤ n ≤ 100000
0 ≤ a i a_i ai 1 0 9 10^9 109

输入示例

5
5
1
3
2
4

输出示例

3

讲解

长度为n的数列A有多达 2 n 2^n 2n个子序列,但我们应用动态规划法仍然可以很高效地求出最长递增子序列(LIS)。这里介绍两个算法:

先考虑用下列变量设计动态规划的算法。这里输入数列的第一个元素为A[1]。
L[n+1]:一维数组,L[i]为由A[1]到A[i]中的部分元素构成且最后选择了A[i]的LIS的长度
P[n+1]:一维数组,P[i]为由A[1]到A[i]中的部分元素构成且最后选择了A[i]的LIS的倒数第二个元素的位置(记录当前已得出的最长递增子序列中,各元素前面一个元素的位置)。
有了这些变量,动态规划法求LIS的算法可以这样实现:

动态规划法求最长递增子序列的算法:

LIS()
	L[0] = 0
	A[0] = 0 //选择小于A[1]到A[n]中任意一个数的值进行初始化
	P[0] = -1
	for i = 1 to n
		k = 0
		for j = 0 to i - 1
			if A[j] < A[i] && L[j] > L[k]
				k = j
			L[i] = L[k] + 1 //满足A[j] < A[i]且L[j]最大的j即为k
			P[i] = k //LIS中A[i]的前一个元素为A[k]

上述动态规划法的复杂度为 O ( n 2 ) O(n^2) O(n2),仍不很理想

实际上,只要把动态规划法与二分搜索结合起来,就能进一步提高效率。这种算法需要下述变量:
L[n]:一维数组,L[i]表示长度为 i + 1的递增子序列的末尾元素最小值
l e n g t h i length_i lengthi:整数,表示前 i 个元素构成的最长递增子序列的长度。

由于我们是按照升序记录的L[j],因此可以通过二分搜索来求L[j](j = 0到length-1)中第一个大于等于A[i]的元素的下标j。这个兼用了二分搜索和动态规划的算法复杂度为 O ( n l o g n ) O(nlogn) O(nlogn),实现如下:

兼用动态规划法和二分搜索求最长递增子序列:

LIS()
	L[0] = A[0]
	length = 1
	for i = 1 to n-1
		if L[length] < A[i]
			L[length++] = A[i]
		else
			L[j] (j = 0, 1, ..., length-1)中第一个大于等于A[i]的元素 = A[i]

AC代码如下

#include<iostream>
#include<algorithm>
#define MAX 100000
using namespace std;

int n, A[MAX+1], L[MAX];

int lis() {
	L[0] = A[0];
	int length = 1;
	
	for(int i = 1; i < n; i++) {
		if(L[length-1] < A[i]) {
			L[length++] = A[i];
		} else {
			*lower_bound(L, L + length, A[i]) = A[i];
		}
	}
	
	return length;
} 

int main(){
	cin>>n;
	for(int i = 0; i < n; i++) {
		cin>>A[i];
	}
	
	cout<<lis()<<endl;
	
	return 0;
}
  • 6
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值