dp-最长递增子序列 (LIS)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了dp-最长递增子序列 (LIS)相关的知识,希望对你有一定的参考价值。

 

首先引出一个例子

问题 :

  给你一个长度为 6 的数组 , 数组元素为 { 1 ,4,5,6,2,3,8 } , 则其最长单调递增子序列为 { 1 , 4 , 5 , 6 , 8 } , 并且长度为 5 。

 

分析 :

  题目所要找的递增子序列 , 想想有什么特点呢 ? 是不是会发现 所有的递增序列 ,前一个数一定小于后一个数 ,并且如果给所有从小到大的数标号 , 会得到一串递增的数 。

  既然是借助动态规划分析问题 , 那么当前的产生的结果 , 仅仅只与前一次状态有关 ,一直推的话 , 那么是不是就很自然地想到我最最简单的问题就是当数组中的元素只有一个的时候 , 并且我还要在开一个数组 , 记录所有元素的位置 。

 

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

#define Min(a,b) a>b?b:a
#define Max(a,b) a>b?a:b


int main ( ) {
    int arr[7] = { 1 , 4 , 5 , 6 , 2 , 3 , 8 } ;
    int pt[10] ;

    for ( int i = 0 ; i < 7 ; i++ )
        pt[i] = 1 ;
    for ( int i = 1 ; i < 7 ; i++ ) {
        for ( int j = 0 ; j < i ; j++ ) {
            if ( arr[i] > arr[j] && pt[j]+1 > pt[i] ) // 注意一定要是 pt[j]+1 > pt[i] 
                pt[i] = pt[j] + 1 ;
        }
    }
    int maxn = 0 ;
    for ( int i = 0 ; i < 7 ; i++ )
        maxn = max ( maxn , pt[i] ) ;

    cout << maxn << endl ;

    return 0 ;
}

 

现在如果要输出这个递增的序列 , 要怎么做呢?

  

int maxn = 0 , t ;
    for ( int i = 0 ; i < 7 ; i++ )
        if ( maxn < dp[i] ) {
            maxn = dp[i] ;
            t = i ;
        }

    for ( int i = 6 ; i >= 0 ; i-- ) {
        if ( dp[i] == maxn ) {
            cout << a[i] << \'\\t\' ;
            int f = a[i] , ff = dp[i] ;
            for ( int j = i-1 ; j >= 0 ; j-- ) {
                if ( f > a[j] && ff == dp[j]+1 ) {
                    cout << a[j] << \'\\t\' ;
                    f = a[j] ;
                    ff = dp[j] ;
                }
            }
        }
    }

 

 

顺便给出 pt[ ] 数组中所存的数据

  

 

优化 :

  上述方法还是很好理解的 , 但是复杂度确实 n^2  , 现在有一种优化手段 , 可以将复杂度优化为 降为 n * log n ,这种方法的核心思想 ,  在二分下写 , 维护一个当前的最优的递增序列  , 找到恰好大于它的更新 。

举个小例子

  比如数组 a[ ] = { 1 , 3 , 2 , 4  } , 现将 a [ 1 ] 放入放入新数组 b [ ] 中 ,则 b[ 0 ] = 1 , 在 取出 a[ 1 ] = 3 , 将其放入 b 数组中 , 因为 3 恰好比 b[ 0 ] 大  , 所以 将 b[ 1 ] = 3  , 在拿出 a[ 2 ]  , 将 2 在数组 b 中二分 , 寻找位置 , 因为 2 恰好位于 1 和 3 之间 , 所以此时要用 2 去替换 3 的位置 ,即在 b 数组中得到一个新的有序的序列  , 但此序列并不是最长递增的子序列 ,它仅仅只是存储对应长度LIS 的最小末尾 。

 

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

#define Min(a,b) a>b?b:a
#define Max(a,b) a>b?a:b

int arr[7] = { 1 , 3 , 6 , 3  } ;
int dp[10] ;

int fun ( int key , int l , int r ) {
    int mid ;
    if ( key >= dp[r] ) {
        return r + 1 ;
    }
    while ( l <= r ) {   // 二分查找,退出循环的前一次情况 , 一定是 l == r,如果 if 的判断里有等号 ,则 l 左移 ,否则 r 右移 
        mid = l + ( r - l ) / 2 ;
        if ( dp[mid] <= key ) l = mid + 1 ;  
        else r = mid - 1 ;
    }

//    printf ( "\\n\\n l = %d r = %d \\n" , l , r ) ;
    return l ;
}

int main ( ) {
    dp[0] = arr[0] ;
    int len = 0 ;
    for ( int i = 1 ; i < 4 ; i++ ) {
        int f = fun ( arr[i] , 0 , len ) ;
        dp[f] = arr[i] ;
        if ( f > len ) len++ ;
    }

    int cnt = 0 ;
    for ( int i = 0 ; i < 4 ; i++ )
        if ( dp[i] > 0 ) cnt++ ;

    cout << cnt << \'\\n\' ;
    return 0 ;
}

 


 

以上是关于dp-最长递增子序列 (LIS)的主要内容,如果未能解决你的问题,请参考以下文章

求解最长递增子序列(LIS) | 动态规划(DP)+ 二分法

动态规划(DP),最长递增子序列(LIS)

dp-最长递增子序列 (LIS)

LeetCode刷题 最长递增子序列

最长递增子序列( LIS)

最长递增子序列的个数--了解两种实现LIS算法的区别