/*
求数组中最长递增子序列:(这里不要求相邻)
写一个时间复杂度尽可能低的程序,求一个一维数组(N个元素)中最长递增子序列的长度。
例如,在序列1,-1,2,-3,4,-5,6,-7中,最长递增子序列的长度为4(如1,2,4,6)
分析:
也就是找一个标号的序列,b[0],b[1],...,b[m] (0<= b[0] < b[1] <... <b[m]<N),使得array[ b[0] ] < array[ b[1] ] < ... < array[ b[m] ]
解法1:
将各阶段按照一定的次序排列好以后,对于某个给定的阶段状态来说,它以前各阶段的状态无法直接影响它未来的决策,只能间接地通过当前状态来影响。
换句话说,每个状态都是过去历史的一个完整总结。
我们以序列1,-1,2,-3,4,-5,6,-7为例,我们在找到4之后,并不关心4之前的两个值具体是怎样,因为它对找到6,没有直接影响。因此,这个问题满足无后效
性,可以使用动态规划来解决。
可以通过数字的规律来分析目标串:
1,-1,2,-3,4,-5,6,-7
使用i来表示当前遍历的位置:
当i=1时,显然,最长的递增序列为(1),序列长度为1
当i=2时,由于-1 < 1。因此,必须丢弃第一个值然后重新建立新串。当前的递增序列为(-1),长度为1
当i=3时,由于2>1,2>-1。因此最长的递增序列为(1,2),(-1,2),长度为2。2前面是1还是-1对求出后面的递增序列没有直接影响。
结论:
假设在目标数组array[]的前i个元素中,最长递增子序列的长度为LIS[i]。那么,
LIS[i+1] = max{1,LIS[k]+1},array[i+1] > array[k],for any k <= i
即如果array[i+1]>array[k],那么第i+1个元素可以接在LIS[k]长的子序列后面构成一个更长的子序列,与此同时,array[i+1]本身至少可以构成一个长度为1
的子序列。
解法2:
当考察第i+1个元素时,我们呢不考虑前面i个元素的分布情况。当考察第i+1的元素的时候考虑前面i个元素的情况。
对于前面i个元素的任何一个递增子序列,如果这个子序列的最大的元素比array[i+1]小,那么就可以将array[i+1]加在这个子序列之后,构成一个新的递增子序列。
比如当i=4的时候,目标序列为:1,-1,2,-3,4,-5,6,-7。最长递增序列为(1,2),(-1,2)。只要4>2,就可以把4直接增加到前面的子序列中形成一个新的递增子序列。
因此,我们希望找到前i个元素中的一个递增子序列,使得这个递增子序列的最大元素比array[i+1]小,且长度尽量长。这样将array[i+1]加在该递增子序列之后,便可以
找到array[i+1]为最大元素的最长递增子序列。
仍然假设在数组的前i个元素中,以array[i]为最大元素的递增子序列长度为LIS[i],同时假设:
长度为1的递增子序列最大元素的最小值为MaxV[1],长度为2的递增子序列最大元素的最小值为MaxV[2]
...
长度为LIS[i]的递增子序列最大元素的最小值为MaxV[LIS[i]]
解法3:
能否把递增序列中间的关系挖掘出来?分析一下临时存储下来的最长递增序列信息。
在递增序列中,如果i<j,那么就会有MaxV[i] < MaxV[j]。如果出现MaxV[j] < MaxV[i]的情况,则跟定义矛盾。
根据单调递增的关系,可以将穷举部分进行如下修改:
for(j = LIS[i-1] ; j >= 1 ; j--) 原来 for(j = iLisLen ; j >= 0 ; j--)
{ {
if(array[i] > MaxV[j]) if(array[i] > iMaxV[j])
{ {
LIS[i] = j + 1; LIS[i] = j + 1;
break; break;
} }
} }
输入:
8
1 -1 2 -3 4 -5 6 -7
输出:
4
*/
/*
关键:
1 for(int i = 0 ; i < iLen ; i++)
{
iLisArr[i] = 1;
for(int j = 0 ; j < i ; j++)//从后向前寻找前面最长的序列
{
if(pArr[i] > pArr[j] && iLisArr[j] + 1 > iLisArr[i])//当前元素大于前面任何一个元素并且当前长度+1大于1,则更新最大长度
{
iLisArr[i] = iLisArr[j] + 1;//LIS[i] = max{1,LIS[j] + 1},array[i+1] > array[k],k<= i
2 int iLisLen = 1;//数组最长递增子序列长度
for(int i = 1 ; i < iLen ; i++)//穷举遍历,时间复杂度仍然为O(n^2)
{
int j;
for(j = iLisLen ; j >= 0 ; j--)//遍历历史最长递增序列信息
{
if(pArr[i] > iMaxV[j])//如果当前元素比递增子序列中的最大元素还要大,说明我们应该更新最长递增子序列中的长度
{
iLisArr[i] = j + 1;
break;//注意,找到后立即跳出
}
}
if(iLisArr[i] > iLisLen)//如果当前序列大于最长递增序列长度,更新最长信息,并且将该元素添加到最长递增子序列中,作为最后面一个元素
{
iLisLen = iLisArr[i];
iMaxV[ iLisArr[i] ] = pArr[i];
}
else if(iMaxV[j] < pArr[i] && pArr[i] < iMaxV[j+1])//如果当前元素大于前一个元素 并且 当前元素小于后一个元素。其实就是更新最大元素的最小值
{
iMaxV[ j + 1 ] = pArr[i];
3 upper_bound:当v存在时返回它出现的最后一个位置的后面的一个位置,如果不存在,返回这样一个下标i:在此处插入
v,原来A[i],A[i+1]...等元素全部后移一个位置后,仍然有序
4 int j = upper_bound(0,iLisLen + 1,pArr[i],iMaxV);
//由于返回的下标比它本身要大,把这里的查询部分改为二分搜索来降低时间复杂度,因为iMaxV刚好是一个有序数组。凡是有查询的地方均可以改成二分搜索
iLisArr[i] = j;
j--;//获取从后向前第一个小于当前元素的下标
*/
#include <stdio.h>
const int MAXSIZE = 10000;
const int INT_MAX = 0x7fffffff;
const int INT_MIN = -INT_MAX;
int LIS(int* pArr,int iLen)
{
int iLisArr[MAXSIZE];
for(int i = 0 ; i < iLen ; i++)
{
iLisArr[i] = 1;
for(int j = 0 ; j < i ; j++)//从后向前寻找前面最长的序列
{
if(pArr[i] > pArr[j] && iLisArr[j] + 1 > iLisArr[i])//当前元素大于前面任何一个元素并且当前长度+1大于1,则更新最大长度
{
iLisArr[i] = iLisArr[j] + 1;//LIS[i] = max{1,LIS[j] + 1},array[i+1] > array[k],k<= i
}
}
}
int iMax = iLisArr[0];
for(int k = 1 ; k < iLen ; k++)
{
if(iLisArr[k] > iMax)
{
iMax = iLisArr[k];
}
}
return iMax;
}
int LIS_max(int* pArr,int iLen)
{
int iMaxV[MAXSIZE];//记录数组中的递增序列信息
iMaxV[0] = INT_MIN;//数组中的最小值,边界值
iMaxV[1] = pArr[0];//数组中的第一个值,边界值
int iLisArr[MAXSIZE];
for(int i = 0 ; i < MAXSIZE ; i++)//初始化最长递增序列信息
{
iLisArr[i] = 1;
}
int iLisLen = 1;//数组最长递增子序列长度
for(int i = 1 ; i < iLen ; i++)//穷举遍历,时间复杂度仍然为O(n^2)
{
int j;
for(j = iLisLen ; j >= 0 ; j--)//遍历历史最长递增序列信息
{
if(pArr[i] > iMaxV[j])//如果当前元素比递增子序列中的最大元素还要大,说明我们应该更新最长递增子序列中的长度
{
iLisArr[i] = j + 1;
break;//注意,找到后立即跳出
}
}
if(iLisArr[i] > iLisLen)//如果当前序列大于最长递增序列长度,更新最长信息,并且将该元素添加到最长递增子序列中,作为最后面一个元素
{
iLisLen = iLisArr[i];
iMaxV[ iLisArr[i] ] = pArr[i];
}
else if(iMaxV[j] < pArr[i] && pArr[i] < iMaxV[j+1])//如果当前元素大于前一个元素 并且 当前元素小于后一个元素。其实就是更新最大元素的最小值
{
iMaxV[ j + 1 ] = pArr[i];
}
}
return iLisLen;
}
/*
upper_bound:当v存在时返回它出现的最后一个位置的后面的一个位置,如果不存在,返回这样一个下标i:在此处插入
v,原来A[i],A[i+1]...等元素全部后移一个位置后,仍然有序
upper_bound上界:如果iVal > iArr[mid],则mid不可能,应该是将下标往后找low = mid + 1
iVal < iArr[mid],则mid可能,下标往前high = mid
iVal = iArr[mid],则mid不可能,下标往后找 low = mid + 1
总体上是: iVal >= iArr[mid],low = mid + 1
iVal < iArr[mid],high = mid
*/
int upper_bound(int low,int high,int iVal,int* pArr)//这里采用寻找上界值,找到的下标返回之后用于LIS[i]长度的更新
{
int mid;
while(low < high)
{
mid = low + (high - low)/2;
if(iVal >= pArr[mid])
{
low = mid + 1;
}
else
{
high = mid;
}
}
return low;
}
int LIS_binarySearch(int* pArr,int iLen)
{
int iMaxV[MAXSIZE];
iMaxV[0] = INT_MIN;
iMaxV[1] = pArr[0];
int iLisArr[MAXSIZE];
for(int i = 0 ; i < MAXSIZE; i++)
{
iLisArr[i] = 1;
}
int iLisLen = 1;
for(int i = 1 ; i < iLen ; i++)
{
int j = upper_bound(0,iLisLen + 1,pArr[i],iMaxV);
//由于返回的下标比它本身要大,把这里的查询部分改为二分搜索来降低时间复杂度,因为iMaxV刚好是一个有序数组。凡是有查询的地方均可以改成二分搜索
iLisArr[i] = j;
j--;//获取从后向前第一个小于当前元素的下标
if(iLisArr[i] > iLisLen)
{
iLisLen = iLisArr[i];
iMaxV[ iLisArr[i] ] = pArr[i];
}
else if(iMaxV[j] < pArr[i] && pArr[i] < iMaxV[j+1])
{
iMaxV[j+1] = pArr[i];
}
}
return iLisLen;
}
void process()
{
int n;
while(EOF != scanf("%d",&n))
{
int iArr[MAXSIZE];
for(int i = 0 ; i < n ; i++)
{
scanf("%d",&iArr[i]);
}
printf("%d\n",LIS_binarySearch(iArr,n));
}
}
int main(int argc,char* argv[])
{
process();
getchar();
return 0;
}
编程之美: 第二章 数字之魅 2.16求数组中最长递增子序列
最新推荐文章于 2015-08-04 01:03:41 发布