码迷,mamicode.com
首页 > 其他好文 > 详细

最长上升子序列LIS解法(n^n && nlogn)

时间:2015-07-22 09:29:15      阅读:130      评论:0      收藏:0      [点我收藏+]

标签:

最长递增子序列问题 在一列数中寻找一些数满足 任意两个数a[i]和a[j] 若i<j 必有a[i]<a[j] 这样最长的子序列称为最长递增子序列LIS

LIS问题有两种常见的解法 一种时间复杂度n^n 一种时间复杂度nlogn 

下面我们先来说一下n^n的算法

设dp[i]表示以i结尾的最长上升子序列的长度 把问题分解 分解成序列中每一项最为终点的最大上升子序列 从第二项开始依次判断 最后找出最大的一项就是答案 则状态转移方程为

dp[i] = max{dp[j]+1}, 1<=j<i,a[j]<a[i].

代码时间参考POJ2533 

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

const int maxn = 1011;
int an[maxn];
int dp[maxn];
int res;

int main(){
    int n;
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i){
        scanf("%d", &an[i]);
    }

    for(int i = 1; i <= n; ++i){
        dp[i] = 1;
        for(int j = 1; j < i; ++j){
            if(an[j] < an[i]){
                dp[i] = max(dp[i], dp[j] + 1);
            }
        }
        res = max(dp[i], res);
    }

    printf("%d\n", res);
    return 0;
}


下面我们来说一下另外一种效率更高的算法 时间复杂度nlogn

假设存在一个序列a[1..9] = 2 1 5 3 6 4 8 9 7 可以看出来它的LIS长度为5

我们定义一个序列dp然后令 i = 1 to 9 逐个考察这个序列

我们用一个变量len来记录现在最长算到多少了

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

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

接着 a[3] = 5 a[3]>dp[1] 所以令dp[1+1]=dp[2]=a[3]=5 就是说长度为2的LIS的最小末尾是5  这时候dp[1..2] = 1 5   len=2

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

继续 a[5] = 6 它在3后面 因为dp[2] = 3 而6在3后面 于是很容易可以推知dp[3] = 6 这时dp[1..3] = 1, 3, 6,还是很容易理解吧 len = 3 

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

第7个 a[7] = 8 它很大 比4大 嗯 于是dp[4] = 8 len变成4了

第8个 a[8] = 9 得到dp[5] = 9 len继续增大到5了

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

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

 注意 这个1,3,4,7,9不是LIS 它只是存储的对应长度LIS的最小末尾 有了这个末尾 我们就可以一个一个地插入数据 虽然最后一个a[9] = 7更新进去对于这组数据没有什么意义 但是如果后面再出现两个数字 8 和 9 那么就可以把8更新到a[5] 9更新到a[6] 得出LIS的长度为6 

然后应该发现一件事情了 在dp中插入数据是有序的 而且是进行替换而不需要挪动——也就是说 我们可以使用二分查找, 将每一个数字的插入时间优化到O(logN)~~~~~于是算法的时间复杂度就降低到了O(NlogN)~!

附上代码实现 先是HDU1950 比较常规的写法

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

const int maxn = 40010;
int an[maxn], dp[maxn], ans;

int main(){
    int t;
    scanf("%d", &t);
    while(t--){
        int n;
        scanf("%d", &n);
        for(int i = 1; i <= n; ++i){
            scanf("%d", &an[i]);
        }
        dp[1] = an[1];
        ans = 1;

        for(int i = 2; i <= n; ++i){
            int st = 1;
            int en = ans + 1;
            while(st < en){
                int mi = (st+en) / 2;
                if(an[i] <= dp[mi]){
                    en = mi;
                }
                else{
                    st = mi + 1;
                }
            }

            dp[en] = an[i];
            if(en == ans + 1){
                ++ans;
            }
        }

        printf("%d\n", ans);
    }
    return 0;
}

接下来是在挑战上面学到的用lower_bound实现二分 题目依然是POJ2553

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int maxn = 1011;
const int inf = 0xffffff;
int an[maxn];
int dp[maxn];

int main(){
    int n;
    scanf("%d", &n);
    for(int i = 0; i < n; ++i){
        scanf("%d", &an[i]);
    }

    fill(dp, dp+n, inf);
    for(int i = 0; i < n; ++i){
        *lower_bound(dp, dp+n, an[i]) = an[i];
    }
    printf("%d\n", lower_bound(dp, dp+n, inf) - dp);
    return 0;
}



版权声明:本文为博主原创文章,未经博主允许不得转载。

最长上升子序列LIS解法(n^n && nlogn)

标签:

原文地址:http://blog.csdn.net/u012431590/article/details/46997161

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!