美文网首页
动态规划 | 最长上升子序列

动态规划 | 最长上升子序列

作者: 一颗白菜_ | 来源:发表于2018-08-03 14:25 被阅读0次

1.问题描述
描述
一个数的序列bi,当b1 < b2 < ... < bS的时候,我们称这个序列是上升的。对于给定的一个序列(a1, a2, ..., aN),我们可以得到一些上升的子序列(ai1, ai2, ..., aiK),这里1 <= i1 < i2 < ... < iK <= N。比如,对于序列(1, 7, 3, 5, 9, 4, 8),有它的一些上升子序列,如(1, 7), (3, 4, 8)等等。这些子序列中最长的长度是4,比如子序列(1, 3, 5, 8).

你的任务,就是对于给定的序列,求出最长上升子序列的长度。
输入
输入的第一行是序列的长度N (1 <= N <= 1000)。第二行给出序列中的N个整数,这些整数的取值范围都在0到10000。
输出
最长上升子序列的长度。
样例输入
7
1 7 3 5 9 4 8
样例输出
4


2.解题思路

此题用动态规划来解决
第一种方法
1)找子问题
一个上升子序列中最右边的那个数,称为该子序列的“终点”。
即可转化成:
“求以ak(k=1,2,3,.....,N)为终点的最长上升子序列的长度”

虽然这个子问题和原问题形式上并不完全一样,但是只要这N个子问题都解决了,那么这N个子问题的解中,最大的那个就是整个问题的解。

2)确定状态
子问题只和一个变量(数字的位置相关),因此序列中数的位置k就是“状态”,而状态k对应的“值”,就是以ak作为“终点”的最长上升子序列的长度。
状态一共有N个。

3)找出状态转移方程
maxLen(k)表示以ak作为“终点”的最长上升子序列的长度

那么: 83B2174318194BFBA80E83B1A23DFC51.jpg
maxLen(k)的值,就是在ak左边,“终点”数值小于ak,且长度最大的那个上升子序列的长度再加1。因为ak左边任何“终点”小于ak的子序列,加上ak后就能形成一个更长的子序列。

程序如下:时间复杂度为O(N^2)

#include <iostream>
#define MaxSize 1001
using namespace std;
int MaxInArray(int * arr,int n);
int main(){
    int n;
    int MaxLen[MaxSize];//以i为终点的最长上升子序列的长度
    int array[MaxSize];
    cin >> n;
    for(int i =1;i<=n;++i){
        cin >> array[i];
        MaxLen[i] = 1;//赋初值 
    }
    for(int i=1;i<=n;++i)
//每次求以第i个数为终点的最长子序列的长度
        for(int j =1;j<=i;++j)
//察看以第j个数为终点的最长上升子序列
            if(array[i] > array[j])
                MaxLen[i] = max(MaxLen[i],MaxLen[j]+1);
    cout << MaxInArray(MaxLen,n);
    return 0;
} 
int MaxInArray(int *arr,int n){
    int Max = arr[1];
    for(int i=2;i<=n;++i)
        if(Max < arr[i])
            Max = arr[i];
    return Max;
}

第二种方法(时间复杂度为O(nlogn))
后来在遇到一道LIS的题,用上面O(nlogn)的过不了,然后了解到下面这种解法:
我们定义一个最小的有序序列lens[],其长度为len。
用题目给的1 7 3 5 9 4 8来举例。
我们定义一个数组a[7]={1,7,3,5,9,4,8}
a[1] = 1,我们把1放进lens[1],然后序列lens的长度len+1,len = 1
a[2] = 7,a[2]>lens[1],我们把7放进lens[2],然后序列lens的长度len+1,len = 2
a[3] = 3,a[3]<lens[2],因为序列lens存放的是最小的有序序列,所以我们把lens[2]替换成
a[3],此时lens[2] = 3,因为此时只是替换,并没有使lens变长,所以len不变
a[4] = 5,a[4]>lens[2],我们把5放进lens[3],然后序列lens的长度len+1,len = 3
a[5] = 9,a[5]>lens[3],我们把9放进lens[4],然后序列lens的长度len+1,len = 4
a[6] = 4,a[6]<lens[3],我们把lens[3]替换成a[6],此时lens[3] = 4,因为此时只是替换,并没有使lens变长,所以len不变
a[7] = 8,a[7]<lens[4],我们把lens[4]替换成a[7],此时lens[4] = 8,因为此时只是替换,并没有使lens变长,所以len不变
最终len为4,和我们所要求的长度结果是一致的,但是此时的有序序列不一定就是我们的最长上升子序列,此方法只能求长度,但是不能用来求真正的最长上升子序列

经过上面的例子,我们可以得出

如果a[i]>lens[len],lens[++len] = a[i]
不然则从lens这有序序列中找出一个下界x(找到一个最大的x满足len[x]<a[i]),然后将lens[x]替换为a[i],此时查找下界用二分查找,即可使整个算法的时间复杂度为O(nlogn)

改进版程序代码:

#include <iostream>
using namespace std;
int arr[100000];
int search(int l,int r,int num,int a[]);
int LIS(int n);
int main(){
    int n;
    cin >> n;
    for(int i=1;i<=n;++i)
        cin >> arr[i];
    cout << LIS(n) <<endl;
    return 0;
}
int search(int l,int r,int num,int a[]){
    int mid;
    while(l<=r){
        mid = (l+r)>>1;
        if(a[mid]<=num)
            l = mid+1;
        else
            r = mid - 1;
    }
    return l;
}
int LIS(int n){
    int * lens = new int [n+1];
    int len = 1;
    lens[1] = arr[1];
    for(int i = 2;i<=n;++i){
        if(arr[i]>lens[len])
            lens[++len] = arr[i];
        else{
            int p = search(1,len,arr[i],lens);
            lens[p] = arr[i]; 
        }
    }
    return len;
}

相关文章

网友评论

      本文标题:动态规划 | 最长上升子序列

      本文链接:https://www.haomeiwen.com/subject/twiyvftx.html