最长上升子序列(动态规划)

本文介绍了求解字符串最长递增子序列问题的两种方法:动态规划和贪心加二分搜索。动态规划方法通过记录每个字符为止的最长递增子序列长度实现,而贪心加二分搜索则通过维护一个递增序列并使用二分法更新来降低时间复杂度。

题目描述
求一个字符串的最长递增子序列的长度。
输入
第一行一个整数0<n<20,表示有n个字符串要处理
随后的n行,每行有一个字符串,该字符串的长度不会超过10000
输出
输出字符串的最长递增子序列的长度
样例输入
3
aaa
ababc
abklmncdefg
样例输出
1
3
7

思路一:动态规划
例子:求 2 7 1 5 6 4 3 8 9 的最长上升子序列。
我们定义d(i) (i∈[1,n])来表示前i个数以A[i]结尾的最长上升子序列长度。
  前1个数 d(1)=1 子序列为2;
  前2个数 7前面有2小于7 d(2)=d(1)+1=2 子序列为2 7
  前3个数 在1前面没有比1更小的,1自身组成长度为1的子序列 d(3)=1 子序列为1
  前4个数 5前面有2小于5 d(4)=d(1)+1=2 子序列为2 5
  前5个数 6前面有2 5小于6 d(5)=d(4)+1=3 子序列为2 5 6
  前6个数 4前面有2小于4 d(6)=d(1)+1=2 子序列为2 4
  前7个数 3前面有2小于3 d(3)=d(1)+1=2 子序列为2 3
  前8个数 8前面有2 5 6小于8 d(8)=d(5)+1=4 子序列为2 5 6 8
  前9个数 9前面有2 5 6 8小于9 d(9)=d(8)+1=5 子序列为2 5 6 8 9
  d(i)=max{d(1),d(2),……,d(i)} 我们可以看出这9个数的LIS为d(9)=5
  总结一下,d(i)就是找以A[i]结尾的,在A[i]之前的最长上升子序列+1,当A[i]之前没有比A[i]更小的数时,d(i)=1。所有的d(i)里面最大的那个就是最长上升子序列。其实说的通俗点,就是每次都向前找比它小的数和比它大的数的位置,将第一个比它大的替换掉,这样操作虽然LIS序列的具体数字可能会变,但是很明显LIS长度还是不变的,因为只是把数替换掉了,并没有改变增加或者减少长度。但是我们通过这种方式是无法求出最长上升子序列具体是什么的,这点和最长公共子序列不同。

a[i]:第i个字符
f[i]:截止到x[i]的最大长度
状态转移方程:
f[i] = max(f[j] + 1, f[i])

时间复杂度 O(n^2)
代码

#include<iostream>
#include<cstring>
using namespace std;
const int N = 1e5 + 10;

char x[N];
int n;
int f[N];

int main()
{
	cin >> x + 1;
	int len = strlen(x+1);
	for(int i = 0; i <= len; i++)
	    f[i] = 1;
	for(int i = 1; i <= len ;i++) 
	{
		for(int j = 1; j < i; j++)
		{
			if(x[i] > x[j])
			{
				if(f[j] + 1 > f[i])
		     	{
				    f[i] = f[j] + 1;
			    }
			} 
		} 
	}
	cout << f[len] << endl;
	
	return 0;
}

思路二:贪心+二分
额外创建一个数组b[],在遍历a[]时,如果a[i]大于b数组的最后一个字符,则把a[i]拷贝到b[]的后边,反之,则在b[]中找到第一个大于等于a[i]的数,并用a[i]替换它(因为a[i]较小,贪心思想,尾字符越小,所能产生最长子序列的可能性越大)。又因为b[]在动态创建过程中总是递增的,所以在只需要二分查找即可。时间复杂度为O(nlogn)。最后b[]元素个数即为所求。

代码

#include<iostream>
#include<cstring> 
using namespace std;

const int N = 1e4 + 10;
int n;
char a[N];
char b[N];
int ans;

//二分寻找第一个大于等于x的位置 
int binary_search(char b[],int l,int r,char x)
{
	while(l < r)
	{
		int mid = (l + r) / 2;
		if(b[mid] >= x)
		    r = mid;
		else
		    l = mid + 1;
	} 
	return l;
}
int main()
{
	scanf("%d",&n);
	while(n--)
	{
		ans = 0;
		scanf("%s",a + 1);
		int alen = strlen(a + 1);
		ans = 1;
		b[1] = a[1];
		for(int i = 2; i <= alen; i++)
		{
			if(a[i] > b[ans]) 
			    b[++ans] = a[i];
			else
			    b[binary_search(b,1,ans,a[i])] = a[i]; 
		}
		cout << ans << endl;
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值