多种姿势求解最长上升子序列(LIS)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/lishang6257/article/details/79680069

问题描述

LIS(Longest Increasing Subsequence)最长上升(不下降)子序列。
数组A包含N个字符(可能包含相同的值)。设S为A的子序列且S中的元素是递增的,则S为A的递增子序列。如果S的长度是所有递增子序列中最长的,则称S为A的最长递增子序列(LIS)。
例如A为:{1 3 2 0 4},1 3 4,1 2 4均为A的LIS。

问题分析与求解

###o( n 2 n^2 )时间复杂度
\qquad LIS作为一个经典的动态规划的例子,可是使用通用的动态规划的解题思路,于是o(n^2)的算法就很容易可以实现,博主这边使用的是$\textbf{“记忆化搜索”} 的方法,即保存重叠的子问题,也就是用数组 a$保存。具体思路如下:
\qquad 在动态规划问题中,我们应该先解决三个问题,第一个是最优子结构(也就是解状态),第二个是重叠子问题的处理,第三个是子问题合并成解。
\qquad 本例中最优子结构的描述应为:对于给定字符串 S [ 0... n ] S[0...n] 中, S [ i ] S[i] 表示S第 i i 个字符。这里设解空间为 a [ 0... n ] a[0...n] ,a[i]表示S的以S[i]为结束字符的LIS的个数。
\qquad 重叠子问题:在求 S [ 0... i ] S[0...i] 的LIS(即a[i])时,应该先求出 a [ 0 ] , a [ 1 ] . . . a [ i 1 ] a[0],a[1]...a[i-1] ,也就是说,在求a[n]过程中,我们重复求解了 a [ 0 ] , a [ 1 ] . . . a [ i n ] a[0],a[1]...a[i-n] ,致使求解效率下降,这里利用$\textbf{“记忆化搜索”} $就能很好的解决这一问题。
\qquad 子问题合并: 从 a [ 0 ] , a [ 1 ] . . . a [ i 1 ] a[0],a[1]...a[i-1] 求解 a [ i ] a[i] 的过程,就是遍历 a [ 0.. m . . . i 1 ] a[0..m...i-1] 找到 S [ i ] > = S [ m ] S[i] >= S[m] 条件下使得a[i]最大,这样就合并了子问题,求得的最优解

###o( n 2 n^2 )代码实现
语言 : c++
IDE : CodeBlocks 16.01

只求了LIS个数

int LIS1(string s)
{
   int sLen = s.length();
   int a[100] = {1};
   for(int i = 0;i < 100;i ++) a[i] = 1;
   for(int i = 0;i < sLen;i ++){
       for(int j = 0;j < i;j ++){
           if(s[j] <= s[i] && a[j] >= a[i]){
               a[i] = a[j] + 1;
           }
       }
   }
   return a[s.length() - 1];
}


求了个数并增加了一个LIS解的输出

string LIS1(string s)
{
    int sLen = s.length();
    int a[100][2];
    for(int i = 0;i < 100;i ++){
        a[i][0] = 1;
        a[i][1] = -1;
    }
    for(int i = 0;i < sLen;i ++){
        for(int j = 0;j < i;j ++){
            if(s[j] <= s[i] && a[j][0] >= a[i][0]){
                a[i][0] = a[j][0] + 1;
                a[i][1] = j;
            }
        }
    }
    int lMax = 0;
    for(int i = 0;i < 100;i ++){
        if(a[i][0] >= a[lMax][0]) lMax = i;
    }
    string res = "";
    while(a[lMax][0] != 1){
        res += s[lMax];
        lMax = a[lMax][1];
    }
    res += s[lMax];
    reverse(res.begin(),res.end());
    return res;
}

###o( n l o g n nlogn )时间复杂度
\qquad 本例很巧妙的利用了stack[0…n],不过这里需要指出的是 s t a c k [ i ] stack[i] 表示长度为 i + 1 i+1 的LIS的结束字符,为了方便搜索,在添加的过程中,有意识的按字符顺序组织stack,这样一来,我们就可以通过二分查找将 o ( n 2 ) o(n^2) 中搜索代价降为 l o g n logn ,于是时间复杂度就降为了 o ( n l o g n ) o(nlogn)

###o( n l o g n nlogn )时间复杂度代码实现

int LIS2(string s){
    char a[100],sLen = s.length(),j = 1;
    a[0] = s[0];
    for(int i = 1;i < sLen;i ++){
        if(s[i] >= a[j - 1]) a[j++] = s[i];
        else{
            int low = 0,high = j - 1;
            while(low < high){
                int between = (high + low)/2;
                if(s[i] > a[between]) low = between + 1;
                else high = between - 1;
            }
            a[low] = s[i];
        }
    }
    for(int i = 0;i < j;i ++) cout << a[i];
    return 0;
}

##完整代码

#include <iostream>
#include <string>
#include <algorithm>
using namespace std;

string LIS1(string s)
{
    int sLen = s.length();
    int a[100][2];
    for(int i = 0;i < 100;i ++){
        a[i][0] = 1;
        a[i][1] = -1;
    }
    for(int i = 0;i < sLen;i ++){
        for(int j = 0;j < i;j ++){
            if(s[j] <= s[i] && a[j][0] >= a[i][0]){
                a[i][0] = a[j][0] + 1;
                a[i][1] = j;
            }
        }
    }
    int lMax = 0;
    for(int i = 0;i < 100;i ++){
        if(a[i][0] >= a[lMax][0]) lMax = i;
    }
    string res = "";
    while(a[lMax][0] != 1){
        res += s[lMax];
        lMax = a[lMax][1];
    }
    res += s[lMax];
    reverse(res.begin(),res.end());
    return res;
}
int LIS1cpy(string s)
{
   int sLen = s.length();
   int a[100] = {1};
   for(int i = 0;i < 100;i ++) a[i] = 1;
   for(int i = 0;i < sLen;i ++){
       for(int j = 0;j < i;j ++){
           if(s[j] <= s[i] && a[j] >= a[i]){
               a[i] = a[j] + 1;
           }
       }
   }
   return a[s.length() - 1];
}

int LIS2(string s){
    char a[100],sLen = s.length(),j = 1;
    a[0] = s[0];
    for(int i = 1;i < sLen;i ++){
        if(s[i] >= a[j - 1]) a[j++] = s[i];
        else{
            int low = 0,high = j - 1;
            while(low < high){
                int between = (high + low)/2;
                if(s[i] > a[between]) low = between + 1;
                else high = between - 1;
            }
            a[low] = s[i];
        }
    }
    return j;
}

int main()
{
    string s;
    cin >> s;
    LIS2(s);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/lishang6257/article/details/79680069