最長遞增子序列:給定一個長度爲N的數組,找出一個最長的單調遞增子序列,子序列不一定連續,但初始順序不能亂。
比如數組A={1,3,4,2,5},其最長遞增子序列爲1,3,4,5
方法一:最長公共子序列法
對於給定長度爲N的數組A:
- 使數組B爲排序後的數組A (O(NlogN))
- 求出A與B的最長公共子序列(LCS) (O(N2))
- 對求得的公共子序列進行去重 (O(N))
例如:A = {1,3,5,4,4,6}
則B = {1,3,4,4,5,6}
最長公共子串C = {1,3,4,4,6}
對C去重得到結果:{1,3,4,6}
方法二:動態規劃法(O(N2))
- 狀態:以第i個數結尾的最長遞增子序列的長度
- 狀態方程: 其中 且 最大。若沒有滿足的a[j]則
- 最後max(dp)即爲最長遞增子序列的長度
- 若要求得最長遞增子序列,可以用另外的數組lastidx記錄上述的“j”
#include<iostream>
using namespace std;
int main()
{
int a[6] = {1,3,5,4,4,6};
int dp[6],lastidx[6];
for(int i = 0;i < 6;i++)
{
dp[i] = 1; lastidx[i] = i;
for(int j = 0;j < i;j++)
{
if(a[j] < a[i] && dp[j] + 1 > dp[i])
{
dp[i] = dp[j] + 1;
lastidx[i] = j;
}
}
}
int maxx = 0, maxi = 0;
for(int i = 0;i < 6;i++)
if(dp[i]>maxx)
{
maxx = dp[i];
maxi = i;
}
int lis[6];
for(int i=maxx-1;i>0;i--)
{
lis[i] = a[maxi];
maxi = lastidx[maxi];
}
cout << "最長公共子串長度:" << maxx << endl;
for(int i=0;i<maxx;i++)
cout << lis[i] << " ";
return 0;
}
輸出結果爲:
最長公共子串長度:4
1 3 5 6
方法三:O(NlogN)算法
對於給定長度N的數組a,聲明數組last[N],last[i]的意義爲:長度爲i的遞增子序列的最後一個數字,在循環數組a時更新這個數組。
比如{1,3,5,4,4,8,6,7}
a[0] = 1, 則長度爲1的LIS爲1,則last[1] = 1 , len = 1;
a[1] = 3, 3大於此前的最後一位1,故目前的最長的LIS可以爲2了,故last[2] = 3, len = 2;
a[2] = 5, 同理,last[3] = 5 , len = 3;
a[3] = 4, 其可以加在5和3之間,所以可以把5替換掉,即last[3] = 4;
a[4] = 4,與last[3]相等了,所以不做變動。
a[5] = 8,比最後一位(5)大,所以last[4] = 8, len = 4;
a[6] = 6,在5和8中間,所以last[4] = 6;
a[7] = 7, 比最後一位6大,所以last[5] = 7, len =5;
最終得到結果LIS長度爲:5,last數組爲:1,3,4,6,7
注意last數組並不一定是LIS,而是對應長度的LIS的最後一位。
而注意到last數組往往是有序的,所以數組元素只有替換而沒有挪動,故每次插入只要用二分查找即可。因此複雜度爲O(N logN)
- 若想得到最長遞增子序列,同樣可以使用數組lastidx和pre,用來標識至此爲止該長度的LIS的最後一個數的下標和當前數字的來源
#include<bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
#define maxn 8
int main()
{
int a[maxn] = {1,3,5,4,4,8,6,7};
int last[maxn], lastidx[maxn], pre[maxn], len=0, maxi;
memset(last,0x3f,sizeof last);
last[0] = -inf;
for(int i=0;i<maxn;i++)
{
int pos = lower_bound(last, last+maxn, a[i])-last; //二分查找
last[pos] = a[i];
lastidx[pos] = i; //該長度的LIS的最後一個數的下標
pre[i] = lastidx[pos-1]; //當前數字的來源(LIS中的前一位,即lastidx[長度-1])
if(pos >= len)
{
len = pos;
maxi = i; //LIS最後一個數字的下標
}
}
int lis[maxn];
cout << "最長公共子串長度:" << len << endl;
for(int i=len-1;i>=0;i--)
{
lis[i] = a[maxi];
maxi = pre[maxi];
}
for(int i=0;i<len;i++)
cout << lis[i] << " ";
return 0;
}
輸出:
最長公共子串長度:5
1 3 4 6 7