算法导论15.4-6:最长单调递增子序列

时间:2021-01-14 09:52:13

参考资料:
《编程之美》2.16
http://blog.pfan.cn/rickone/13086.html
http://chriszeng87.iteye.com/blog/1054321

题目:给出一个O(nlogn)的算法,使之能够找出一个n个数的序列中最长的单调递增子序列。

O(n2)的比较好理解,没有仔细研究,研究了下O(nlogn)的解答和好多大神的分析才明白一些,发现玄妙无限呀~

对于序列Sn,考虑其长度为i的单调子列(1<=i<=m)。我们选取这些子列的最后一个元素的最小值。用Li表示。则有L1<=L2<=…<=Lm。

序列a1, a2, …, an,从左至右扫描序列(可用二分法),对于每一个ai,它可能

(1)    ai<L1,那么L1=ai

(2)    ai>=Lm,那么Lm+1=ai,m=m+1 (其中m是当前见到的最大的L下标)

(3)    Ls<=ai<Ls+1,那么Ls+1=ai

以上就是binarySrh中完成的主要任务。

  数组a:1 3 5 2 3

1、修改后二分查找可得最长子序列的长度,得数组maxL:1 2 3。最终maxL的长度len即表示最长递增子序列的长度。这个我也只能理解,但不知道怎么证明。

2、如果要得到子序列的内容应该怎么做呢?

可以在遍历过程中,用mem数组记录数字j,j表示当前数字是长度为j的子序列的尾数字。遍历结束后,从后往前遍历mem即可得到长度为len, len-1, len-2, ... 1的所有子序列的结尾数字,这样就得到了最长递增子序列。

 
#include<iostream>
using namespace std;

int binarySrh( int *s, int len, int x ) #二分查找
{
	int left = 0,right = len-1, mid = (left+right)/2;
	while( left<=right )
	{
		if( x>s[mid] )	left = mid+1;
		else if( x<s[mid] ) right = mid-1;
		else return mid;
		mid = (left+right)/2;
	}
	cout << left << ' ';
	return left;
}

int main()
{
	int n = 10;//原始数组长度
	int a[10] = { 6, 1, 8, 3, 10, 11, 12, 13,  4, 5 };
	int maxL[10] = { 0 };//maxL[i]记录长度为i子序列的最小尾数
	int mem[10] = { 0 };//mem[i]表示以a[i]结尾的最长子序列
	maxL[0] = a[0];
	int len = 1;//length
	
	for( int i=1; i<n; i++ )
	{
		int j = binarySrh( maxL, len, a[i] );
		maxL[j] = a[i];
		mem[i] = j;
		if( ++j>len )
			len = j;
	}
	cout << "max length: " << len << endl;
	cout << "longest subsequence: ";
	int currMaxLen = len-1;
	for( int i=0; i<n; i++ )
		cout << mem[i] << " ";
	cout << endl;
	for( int i=9; i>=0&&currMaxLen>=0; i-- ) {
		if( mem[i]==currMaxLen ) {
			cout << a[i] << " "; //反向输出子序列
			currMaxLen--;
		}
	}
	cout << endl;
	return 0; 
}