您的位置:首页 > 其它

最长的单调自增子序列(不一定连续)和

2015-03-22 00:56 232 查看
问题:给定一个长度为N的数组,找出一个最长的单调自增子序列(不一定连续),如序列 1,-1,2,-3,4,-5,6,-7的最长递增子序列长度为4(1,2,4,6)

方法一:
最笨算法,复杂度为O(n*n),设一个辅助数组用来记录以对应元素结尾的最大递增子序列的长度(即lis[i]表示
以array[i]结尾的最大递增子序列长度为lis[i]),从头到尾扫一遍原数组,对于每个元素,以其结尾的最大递增子序列长度要么为1,要么为这个元素前面的元素中小于该元素且最大递增子序列长度最大的那个元素对应的最大递增子序列长度+1。

int LIS(int *array,int arraySize)
{
int lis[arraySize],i,j;
for(i=0;i<arraySize;i++)
{
lis[i]=1;
for(j=0;j<i;j++)
{
if(array[i]>array[j] && lis[j]+1>lis[i])
lis[i] = lis[j] + 1;
}
}
int max=0;
for(i=0;i
if(lis[i]>max)
max = lis[i];
return max;
}

方法二:

复杂度为O(nlogn)的算法,其思想是设一个辅助数组temp,temp[i]表示长度为i+1(也就是程序中的len)的最长递增子序列的最小末尾,则显然temp是有序的。遍历原数组中的每一个元素,对于每个元素key,若比temp数组中当前最后一个元素大,则直接插入temp数组,表示最长递增子序列长度可以增加一,新的LIS以这个元素结尾;否则要么temp中已包含key这个元素,则不做任何操作,要么temp中的数全部大于这个元素,则key取代temp[0]成为长度为1的LIS的结尾元素,要么必然存在temp[j]<key<temp[j+1],这时key应取代temp[j+1]成为长度为j+2的LIS的结尾元素,对于后两种情况(temp中的数全部大于这个元素或temp[j]<key<temp[j+1]),key实际是取代了temp中比key大的和key最接近的元素。最后temp数组的长度(len)即为所求。

int LIS1(int *array,int arraySize)
{
int temp[arraySize],i,pos;
temp[0]=array[0];
int len=1;//用于标示temp数组中的元素个数
for(i=1;i<arraySize;i++)
{
if(array[i]>temp[len-1]) //如果大于temp中最大的元素,则直接插入到temp数组末尾
{
temp[len] = array[i];
len++;
}
else
{
pos = BiSearch(temp,len,array[i]); //二分查找需要插入的位置
temp[pos] = array[i];
}
}
return len;
}

LIS1()用到的修改后的二分查找,该函数在查找成功的时候返回key所在的位置,查找不成功的时候返回
比key大的和Key最接近的元素所在的位置,即key应该插入的位置

int BiSearch(int *data,int len,int key)
{
int low=0,high=len-1,mid;
while(low<=high)
{
mid = (low+high)/2;
if(data[mid]>key)
high = mid-1;
else if(data[mid]
low = mid+1;
else
return mid;
}
return low; //数组data中不存在该元素,则返回该元素应该插入的位置
}

主程序,输出应为两种方法计算的LIS,为4 4

int main()
{
int array[]={1,-1,2,-3,4,-5,6,-7};
int lis = LIS(array,sizeof(array)/sizeof(array[0]));
int lis1 = LIS1(array,sizeof(array)/sizeof(array[0]));
printf("%d %d\n",lis,lis1);
return 0;
}

LIS1()算法的一个示例:
假设存在一个序列d[1..9] ={ 2,1 ,5 ,3 ,6,4, 8 ,9, 7},可以看出来它的LIS长度为5。
下面一步一步试着找出它。
我们定义一个序列B,然后令 i = 1 to 9 逐个考察这个序列。
此外,我们用一个变量Len来记录现在最长算到多少了

首先,把d[1]有序地放到B里,令B[1] = 2,就是说当只有1一个数字2的时候,长度为1的LIS的最小末尾是2。这时Len=1

然后,把d[2]有序地放到B里,令B[1] = 1,就是说长度为1的LIS的最小末尾是1,d[1]=2已经没用了,很容易理解吧。这时Len=1

接着,d[3] = 5,d[3]>B[1],所以令B[1+1]=B[2]=d[3]=5,就是说长度为2的LIS的最小末尾是5,很容易理解吧。这时候B[1..2] = 1, 5,Len=2

再来,d[4] = 3,它正好加在1,5之间,放在1的位置显然不合适,因为1小于3,长度为1的LIS最小末尾应该是1,这样很容易推知,长度为2的LIS最小末尾是3,于是可以把5淘汰掉,这时候B[1..2] = 1, 3,Len = 2

继续,d[5] = 6,它在3后面,因为B[2] = 3, 而6在3后面,于是很容易可以推知B[3] = 6, 这时B[1..3] = 1, 3, 6,还是很容易理解吧? Len = 3 了噢。

第6个, d[6] = 4,你看它在3和6之间,于是我们就可以把6替换掉,得到B[3] = 4。B[1..3] = 1, 3, 4, Len继续等于3

第7个, d[7] = 8,它很大,比4大,嗯。于是B[4] = 8。Len变成4了

第8个, d[8] = 9,得到B[5] = 9,嗯。Len继续增大,到5了。

最后一个, d[9] = 7,它在B[3] = 4和B[4] = 8之间,所以我们知道,最新的B[4] =7,B[1..5] = 1, 3, 4, 7, 9,Len = 5。

于是我们知道了LIS的长度为5。



《编程之美》里有个题目是要求数组中最长递增子序列,在CSDN上看到的题目是数组中的最长递减子序列。题目如下:

求一个数组的最长递减子序列

比如{9,4,3,2,5,4,3,2}的最长递减子序列为{9,5,4,3,2}

求一个数组的最长递增子序列

比如{1,-1,2,-3,4,-5,6,-7}的最长递减子序列为{1,2,4,3,6}

最长递增序列和最长递减子序列的解法是一样的,最不济,也可以先revert,求完再revert一次。

首先我们得搞清一个范围问题,虽然我们要的是一个最大值,但是是不是他是可以递推的呢,就是我只保存一个最大值,然后不断增加这个值,直觉上看应该是不太现实的,比如:12345-101234,我们数到5时最大值是5,但往后看时,都比5小,不能增加最大值,但事实上是最大值是6,如果我们受限于前面求的的一个最大值,就会产生错误的结果,也许你可以说,我就是保存一个值,没到一个点我就重新算新的最大值,虽然牺牲一点计算,但是我就想只有一个变量,有思路吗?如果没有就换个思路吧。既然递推困难,我们就把各个备选值都列出来,然后找出那个最大的,那哪些是备选值呢?其实以任意元素为终点的递增序列都是合法的candidate。

解法1:

如同上面的分析,考虑一个序列x0, x1,x2,… xn-1, xn。我们想知道以每个元素为终点的最长递增子序列的长度,令temp[i]表示以xi为终点的递增序列的长度,假设已经知道了temp[0]-temp[n-1],那如何求temp
呢?如果xi<xn那,xi就有可能在xn为终点的序列上,那以xn为终点的递增序列就至少是xi的最长序列+1。下面是代码:

#include <iostream>
using namespace std;

int LIS(int arr[],int n)
{
    int *temp = new int
;//存放当前遍历位置最长序列 
    for(int i=0;i<n;++i)
    {
        temp[i]=1;   //初始化默认长度 
        for(int j=0;j<i;++j) //找出前面最长的序列 
        {
            // 当前值 array[i] 跟已经遍历的值比较,
            //大于已经遍历的值且已知递增序列+1 大于当前值则 更新当前最长递增序列值 
            if(arr[i]>arr[j]  && temp[j]+1 > temp[i] )
            {
                temp[i] = temp[j] + 1;
            }
            
        }
    }
    
    int max=temp[0];
    for(int k=0;k<n;++k)//找出整个数组中最长的子序列 
    {
        if(max<temp[k])
            max=temp[k];
    }
    
    return max;
    
}

int main()
{
    int arr[]={1,-1,2,-3,4,-5,6,-7};
    int result=LIS(arr,8);
    cout<<result<<endl;
    
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: