一、最长递增序列的问题描述:
求一个整数序列的最长递增子序列,子序列不要求是连续的。例如:
Input:4,6,9,6,7,6,3,8,10;Output:5
二、解决方法:
1、用动态规划的方法解决。从问题我们可以知道,我们最终得到的最长递增子序列,其任意一段子序列也是对应序列中的最长子序列。这样说可能不好理解,就以上面的例子来说:
最长子序列为:4,6, 7, 8, 10。在这段子序列的子序列里选一个,例如:4,6,7。则4,6,7也是4,6,9,6,7,6,3这段序列的最长子序列。
对于动态规划思想,我们最重要的是找到状态和状态转移方程,那我们来看看这里怎么得到这两个关键东西:
假设,nums={4,6,9,6,7,6,3,8,10},f(x)表示前x个数以nums[x]结尾的序列中,最长子序列的长度,那么:
f(1)=1;f(2)=max{f(1)+1,1}=2;f(3)=max{f(2)+1,1},……以此类推。这里或许有人问了,为什么是f(x-1)+1和1比较啊?原因很简单:以f(2)为例,f(2)=f(1+1),而f(1)=1,也就是说,f(2)的结果是和f(1)关联的,而f(2)本身具有一个初始值1,根据问题要求f(2)的最终值应该是通过f(1)运算所得值和它的初始值选大的那一个。后面的也就以此类推了。
2、这种方法非常巧妙,主要思想是这样的,我不管你的序列是由什么组成的,我只在乎这个序列的长度是多少。于是我创建一个数组d[length],d[0]用来存储最长序列长度,d[i]表示最长递增序列长度为i时,序列的结尾元素。通过二分查找不断的为元素找插入的位置,构成一个最长序列。
这种解法的关键在于:我们要获得的最长递增序列,事实上只要保证每个位置的数字足够小,而且有序,那么得到的自然是最长子序列。说的可能不太清楚,大家可以通过代码感受一下我的意思。
三、代码:
1、O(n^2)的DP算法
int lis1(int* nums, int length){
int lis_len = 1;
int d[9];
for (int i = 0; i < length; ++i){
d[i] = 1;
for (int j = 0; j < i; ++j)
if (nums[i] >= nums[j] && d[i] < d[j] + 1)
++d[i];
if (lis_len < d[i])
lis_len = d[i];
}
return lis_len;
}
2、O(nlogn)的二分思想算法
int lis2(int* nums, int length){
int d[9];
int mid, left, right;
d[0] = 1;
d[1] = nums[0];
for (int i = 1; i < length; ++i){
left = 1;
right = d[0];
while (left <= right){
mid = (left + right) / 2;
if (nums[i] > d[mid])
left = mid + 1;
else
right = mid - 1;
}
d[left] = nums[i];
if (left > d[0])
++d[0];
}
return d[0];
}