1. 程式人生 > >最長上升子序列(Longest Increasing Subsequence)問題(兩種解法)

最長上升子序列(Longest Increasing Subsequence)問題(兩種解法)

前言

本篇部落格主要介紹了有關最長上升子序列(LIS)的三種DP解決方法,分別是O(n^2)和O(nlogn)(貪心加二分)兩種DP

問題介紹

對於一個有序序列x1,x2,x3,...,xn 我們可以從其中得到一序列ai1,ai2,ai3,...,aim滿足ai1<ai2<ai3<...<aim1i1<i2<i3<...<imn這樣的字序列被稱為該序列的上升子序列,也稱單調遞增子序列,而我們目標就是求一個序列中最長的單調遞增的子序列。
如對於一有序序列{1,6,2,5,4,2,1,6},{1,5,6}為其一的上升子序列,而{1,2,4,6}就為其最長上升子序列.

求解方法

1.O(n2)樸素演算法

那麼既然這是一道DP問題,我們可以發現它具有無後效性和最優子結構,因為它這個序列首先不會改變,而對於它的子序列求出的LIS會也會對答案有貢獻。
那麼f[i]定義為:以xi結尾的最長上升子序列的長度
我們首先可以發現,單個位置的f[i]肯定為1因為它可以以自己作為一[1,i]的上升子序列
然後我們假設f[1],f[2],…,f[i-1]的值都已求出,而我們的目標就是求f[i]的值.也就是要求狀態轉移方程式.
我們現在已知道在1~j(1<=j< i)的最長上升子序列,我們的目標就是在一合法的1~j且該子序列的LIS最長的後新增一i

,那是不是如果xj<xi就可以進行拼接?也就是轉移。那我們就可以得到狀態轉移方程式;

f[i]=max(f[j])+1(1<=j<iandxj<xi)
最後答案就是f[n]

程式碼

#include<cstdio>
#include<algorithm>
#define LL long long
using namespace std;
int read(){
    int f=1,x=0;char s=getchar();   
    while
(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();} while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();} return x*f; } #define MAXN 1000 int x[MAXN+5],f[MAXN+5]; int main() { int n=read(),ans=1; for(int i=1;i<=n;i++) x[i]=read(); for(int i=1;i<=n;i++){ f[i]=1; for(int j=1;j<=i-1;j++) if(x[j]<x[i]&&f[j]+1>f[i]) f[i]=f[j]+1; ans=max(ans,f[i]); } printf("%d\n",ans); return 0; }

2.O(n·logn)貪心+二分優化

我們的狀態定義就會變得跟之前的不一樣了,但我們還是可以通過剛才的定義來觀察一下:
我們對於i將會找到它之前滿足條件的最大的f[j]轉移過來也就是長度最大,那其實我們可以來建一個一長度為下標的陣列f,我們的目標是每次能直接將low陣列中小於i的最大的一個下標直接加1就能得到1~i中以i結尾的最長的LIS,f[i]在這裡定義為:
f[i]:i
因為這樣是一貪心策略,對於一定位置i,當一上升子序列長度一定是,末尾元素越小越利於後面元素的選擇
也就是大於等於i的第一個數的下標,同時由於這裡i在這裡必然小於f[i]還可以順帶更新f[i],我們這樣做了過後整個f陣列是呈嚴格上升的,於是我們就能直接二分答案,在這裡我們可以直接呼叫algoriethm裡的lower_bound函式,二分一次low陣列的時間複雜度的O(logn),所以總的時間複雜度是O(n·logn)
來一次完整演示:
這是原陣列
圖片
首先將f[1]更新
圖片
然後發現f[1]還是比當前元素大,繼續更新
圖片
同上
圖片
這裡發現2比1大,於是更新了f[2]
圖片
同上
圖片
這裡最後就返回了3
圖片
程式寫起來也極為簡潔.

程式碼

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int read(){
    int f=1,x=0;char s=getchar();   
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}  
    while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
    return x*f;
}
#define MAXN 1000
#define INF 0x3f3f3f3f
int a[MAXN+5],dp[MAXN+5];
int main(){
    int n=read();
    memset(dp,0x3f,sizeof(dp));
    for(int i=1;i<=n;i++)
        a[i]=read(),*lower_bound(dp+1,dp+n+1,a[i])=a[i];
    printf("%d\n",lower_bound(dp+1,dp+n+1,INF)-(dp+1));
    return 0;
}