最长上升子序列(LIS)算法
LIS定義
LIS(Longest Increasing Subsequence)最長上升子序列?
一個數(shù)的序列bi,當(dāng)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).?
你的任務(wù),就是對于給定的序列,求出最長上升子序列的長度。
兩種做法
O(N^2)做法:dp動態(tài)規(guī)劃
狀態(tài)設(shè)計(jì):dp[i]代表以a[i]結(jié)尾的LIS的長度?
狀態(tài)轉(zhuǎn)移:dp[i]=max(dp[i], dp[j]+1) (0<=j< i, a[j]< a[i])?
邊界處理:dp[i]=1 (0<=j< n)?
時間復(fù)雜度:O(N^2)?
舉例: 對于序列(1, 7, 3, 5, 9, 4, 8),dp的變化過程如下
| dp[0] | 1 | ? | ? | ? | ? | ? | ? |
| dp[1] | 1 | 2 | ? | ? | ? | ? | ? |
| dp[2] | 1 | 2 | 2 | ? | ? | ? | ? |
| dp[3] | 1 | 2 | 2 | 3 | ? | ? | ? |
| dp[4] | 1 | 2 | 3 | 3 | 4 | ? | ? |
| dp[5] | 1 | 2 | 2 | 3 | 3 | 3 | ? |
| dp[6] | 1 | 2 | 3 | 3 | 4 | 4 | 4 |
求完dp數(shù)組后,取其中的最大值就是LIS的長度。【注意答案不是dp[n-1],這個樣例只是巧合】
#include<stdio.h> #include<iostream> #include<string.h> #include<queue> #include<cstdio> #include<string> #include<math.h> #include<algorithm> #include<map> #include<set> #include<stack> #define mod 998244353 #define INF 0x3f3f3f3f #define eps 1e-6 using namespace std; typedef long long ll; using namespace std; const int MAXX=10000+5;int a[MAXX],dp[MAXX]; // a數(shù)組為數(shù)據(jù),dp[i]表示以a[i]結(jié)尾的最長遞增子序列長度 int n; int LIS(){int ans=1;for(int i=1; i<=n; i++)//枚舉子序列的終點(diǎn){dp[i]=1;// 初始化為1,長度最短為自身for(int j=1; j<i; j++)//從頭向終點(diǎn)檢查每一個元素{if(a[i]>a[j]){dp[i]=max(dp[i],dp[j]+1); // 狀態(tài)轉(zhuǎn)移}}ans=max(ans,dp[i]); // 比較每一個dp[i],最大值為答案}return ans; } int main() {while(cin>>n){for(int i=1; i<=n; i++){cin>>a[i];}int ans=LIS();cout<<ans<<endl;}return 0; }O(NlogN)做法:貪心+二分
a[i]表示第i個數(shù)據(jù)。?
dp[i]表示表示長度為i+1的LIS結(jié)尾元素的最小值。?
利用貪心的思想,對于一個上升子序列,顯然當(dāng)前最后一個元素越小,越有利于添加新的元素,這樣LIS長度自然更長。?
因此,我們只需要維護(hù)dp數(shù)組,其表示的就是長度為i+1的LIS結(jié)尾元素的最小值,保證每一位都是最小值,
這樣子dp數(shù)組的長度就是LIS的長度。
dp數(shù)組具體維護(hù)過程同樣舉例講解更為清晰。?
同樣對于序列 a(1, 7, 3, 5, 9, 4, 8),dp的變化過程如下:
- dp[0] = a[0] = 1,長度為1的LIS結(jié)尾元素的最小值自然沒得挑,就是第一個數(shù)。 (dp = {1})
- 對于a[1]=7,a[1]>dp[0],因此直接添加到dp尾,dp[1]=a[1]。(dp = {1, 7})
- 對于a[2]=3,dp[0]< a[2]< dp[1],因此a[2]替換dp[1],令dp[1]=a[2],因?yàn)殚L度為2的LIS,結(jié)尾元素自然是3好過于7,因?yàn)樵叫∵@樣有利于后續(xù)添加新元素。 (dp = {1, 3})
- 對于a[3]=5,a[3]>dp[1],因此直接添加到dp尾,dp[2]=a[3]。 (dp = {1, 3, 5})
- 對于a[4]=9,a[4]>dp[2],因此同樣直接添加到dp尾,dp[3]=a[9]。 (dp = {1, 3, 5, 9})
- 對于a[5]=4,dp[1]< a[5]< dp[2],因此a[5]替換值為5的dp[2],因此長度為3的LIS,結(jié)尾元素為4會比5好,越小越好嘛。(dp = {1, 3, 4, 9})
- 對于a[6]=8,dp[2]< a[6]< dp[3],同理a[6]替換值為9的dp[3],道理你懂。 (dp = {1, 3, 5, 8})
這樣子dp數(shù)組就維護(hù)完畢,所求LIS長度就是dp數(shù)組長度4。?
通過上述求解,可以發(fā)現(xiàn)dp數(shù)組是單調(diào)遞增的,因此對于每一個a[i],先判斷是否可以直接插入到dp數(shù)組尾部,
即比較其與dp數(shù)組的最大值即最后一位;如果不可以,則找出dp中第一個大于等于a[i]的位置,用a[i]替換之。?
這個過程可以利用二分查找,因此查找時間復(fù)雜度為O(logN),所以總的時間復(fù)雜度為O(N*logN)
最長上升子序列
,即整個序列嚴(yán)格遞增
最長不下降子序列,也叫最長非遞減子序列
HDU5532
把每個數(shù)字減去對應(yīng)位置的編號,然后求最長非遞減子序列長度即可
#include <cstdio> #include <cstring> #include <algorithm> #include<iostream> using namespace std; #define INF 0x3f3f3f3f typedef long long LL; int n; const int maxn=1e5+10; int a[maxn],dp[maxn]; int LIS(){int pos=0;dp[0]=a[0];for(int i=1;i<n;i++){if(a[i]>=dp[pos])//改變1:將大于該為大于等于dp[++pos]=a[i];else//改變2:查詢dp數(shù)組中第一個大于a[i]的位置,用a[i]代替dp[upper_bound(dp,dp+pos+1,a[i])-dp]=a[i];}return pos+1; } int main(){int T;scanf("%d",&T);int ca=1;while(T--){scanf("%d",&n);for(int i=0;i<n;i++){scanf("%d",&a[i]);a[i]-=i;dp[i]=INF;}int len=LIS();printf("Case #%d:\n",ca++);printf("%d\n",n-len);}return 0; }?
總結(jié)
以上是生活随笔為你收集整理的最长上升子序列(LIS)算法的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 入侵和反击 动态规划
- 下一篇: 创客更新装备 动态规划