最長遞增子序列LIS和最長公共子序列LCS
本文參考了《程式設計之美》、LeetCode中文題解以及部落格
https://blog.csdn.net/George__Yu/article/details/75896330 (LIS)
https://blog.csdn.net/v_july_v/article/details/6695482 (LCS)
https://blog.csdn.net/SongBai1997/article/details/81866559(LCS)
特此感謝!
一、最長遞增子序列
問題描述
求一個給定序列中最長的遞增子序列的長度。
比如,對於序列(1, 7, 3, 5, 9, 4, 8),有它的一些上升子序列,如(1, 7), (3, 4, 8)等等。這些子序列中最長的長度是4,比如子序列(1, 3, 5, 8).
方法一:動態規劃
時間複雜度:O(n^2)
思路:
狀態設計:F[i]代表以A[i]結尾的LIS的長度
狀態轉移:F[i]=max{F[j]+1}(1<=j< i,A[j]< A[i])
邊界處理:F[i]=1(1<=i<=n)
程式碼:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <cmath>
using namespace std;
const int maxn = 103,INF=0x7F7F7F;
int a[maxn],dp[maxn];
int n,ans = -INF;
int main()
{
while(scanf("%d",&n) !=EOF)
{
for(int i=1;i<=n;++i)
{
scanf("%d",&a[i]);
//初始值為1,即以i結尾的LIS子序列長度至少為1
dp[i] = 1;
}
for (int i=1;i<=n;++i)
for(int j=1;j<i;j++)
{
//要求以a[x]結尾的最長遞增子序列長度,我們依次比較a[x]與之前所有的a[i](i<x)
if(a[j]<a[i]) dp[i]=max(dp[i],dp[j]+1);
}
for(int i=1;i<=n;++i)
ans = max(ans,dp[i]);
printf("%d\n",ans);
}
}
/*
6
1 4 3 2 6 5
*/
方法二:貪心+二分
時間複雜度:O(n^2)
思路:
新建一個low陣列,low[i]表示長度為i的LIS結尾元素的最小值。對於一個上升子序列,顯然其結尾元素越小,越有利於在後面接其他的元素,也就越可能變得更長。
因此,我們只需要維護low陣列:
1)追加low陣列
對於每一個a[i],如果a[i] > low[當前最長的LIS長度],就把a[i]接到當前最長的LIS後面,即low[++當前最長的LIS長度]=a[i]。
2)更新low陣列
對於每一個a[i],如果a[i]能接到LIS後面,就接上去;否則,就用a[i]取更新low陣列。
具體方法是:
在low陣列中找到第一個大於等於a[i]的元素low[j],用a[i]去更新low[j]。如果從頭到尾掃一遍low陣列的話,時間複雜度仍是O(n^2)。我們注意到low陣列內部一定是單調不降的,所有我們可以二分low陣列,找出第一個大於等於a[i]的元素。二分一次low陣列的時間複雜度的O(lgn),所以總的時間複雜度是O(nlogn)。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <cmath>
using namespace std;
const int maxn =300003,INF=0x7f7f7f7f;
int low[maxn],a[maxn];
int n,ans;
int binary_search(int *a,int r,int x)
//二分查詢,返回a陣列中第一個>=x的位置
{
int l=1,mid;
while(l<=r)
{
mid=(l+r)>>1;
if(a[mid]<=x)
l=mid+1;
else
r=mid-1;
}
return l;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
low[i]=INF;//由於low中存的是最小值,所以low初始化為INF
}
low[1]=a[1];
ans=1;//初始時LIS長度為1
for(int i=2;i<=n;i++)
{
if(a[i]>=low[ans])//若a[i]>=low[ans],直接把a[i]接到後面
low[++ans]=a[i];
else //否則,找到low中第一個>=a[i]的位置low[j],用a[i]更新low[j]
low[binary_search(low,ans,a[i])]=a[i];
}
printf("%d\n",ans);//輸出答案
return 0;
}
二、最長公共子序列
問題描述
求兩字元序列的最長公共字元子序列
例如,字串s1=mzjawxu
,s2=xmjyauz
,仔細分析下,大體可以看出最長公共子序列是mjau
。
程式碼
#include <iostream>
#include <vector>
#include <string>
using namespace std;
//公共子序列(非連續)
int dp[1000][1000];
int LcsNotContinus(string x,string y)
{
for(int i=0;i<=x.length();i++)
for(int j=0;j<=y.length();j++)
if(i==0||j==0)
dp[i][j]=0;
else if(x[i-1]==y[j-1])
dp[i][j]=dp[i-1][j-1]+1;
else
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
return dp[x.length()][y.length()];
}
//公共子串(連續)
int LcsContinus(string x,string y)
{
int ans=0;
for(int i=0;i<=x.length();i++)
for(int j=0;j<=y.length();j++)
if(i==0||j==0||x[i-1]!=y[j-1])
dp[i][j]=0;
else{
dp[i][j]=dp[i-1][j-1]+1;
ans=max(ans,dp[i][j]);
}
return ans;
}
int main()
{
string x = "abcdef";
string y = "aebfc";
cout<<LcsNotContinus(x,y)<<endl; //3
cout<<LcsContinus(x,y)<<endl; //1
return 0;
}
公共子串要求連續,而公共子序列不要求連續,這裡參考文章:最長公共子串,使用基本方法、DP、字尾陣列等方法解決。
附:
LIS的問題可以通過LCS解決: