写在前边:
如果需要记录路径,在每一次dp更新的情况下再更新路径,那么math函数就不能用了,需要手写,把路径更新卸载if体中。
1.O(n^2)的dp(两种写法)
写法一:
dp[i]为长度为i+1的上升子序列的最小尾元素
遍历a[]进行更新。如果a[i]>dp[j],则dp[j+1]=min(dp[j+1],a[i])且dp[0]=min(a[i],dp[0])。
int max=0;
int INF=(1<<30)-1;
int dp[]=new int[n];//dp[i]为长度为i+1的上升子序列的最小尾元素
for(int i=0;i<n;i++) {
dp[i]=INF;
}
for(int i=0;i<n;i++) {
for(int j=0;j<n-1;j++) {
if(j==0) {
dp[j]=Math.min(a[i], dp[j]);
}
if(a[i]>dp[j]) {
dp[j+1]=Math.min(a[i], dp[j+1]);
}
}
}
for(int i=0;i<n;i++) {
if(dp[i]!=INF&&i>max) {
max=i;
}
}
写法二:
dp[i]为以a[i]结尾的最长上升子序列长度。
dp[i]=max(dp[i],dp[j]+1)(j<i且a[j]<a[i])
int max=0;
int dp[]=new int[n];
for(int i=0;i<n;i++) {
dp[i]=1;//初始化
for(int j=0;j<i;j++) {
if(a[j]<a[i]) {
dp[i]=Math.max(dp[i], dp[j]+1);
}
}
max=Math.max(max,dp[i]);
}
2.O(nlogn)的二分搜索
新建一个low数组,low[i]表示长度为i的LIS结尾元素的最小值。对于一个上升子序列,显然其结尾元素越小,越有利于在后面接其他的元素,也就越可能变得更长。因此,我们只需要维护low数组:
ans表示当前最长的LIS长度。显然low[1]~low[ans]是严格递增的。
对于每一个a[i],如果a[i] > low[ans],则low[++ans]=a[i];否则,利用二分搜索找到low数组中大于等于a[i]的最小值对应的下标j(即长度j),令low[j]=a[i].
最后ans即为整个数组a的LIS长度。
public static int low[]=new int[maxn];//low[i]表示长度为i的LIS的结尾最小值.;一定是严格递增的!
public static int query(int x,int r) {
int l=0;
while(r-l>1) {
int mid=(l+r)/2;
if(low[mid]<x) {
l=mid;
}else {
r=mid;
}
}
return r;
}
public static void main(String args[]) {
int ans=1;
low[1]=arr[0];
for(int i=1;i<n;i++) {
//找到LIS最小尾元素第一个大于等于arr[i]的下标.
if(arr[i]>=low[ans]) {
low[++ans]=arr[i];
}else {
low[query(arr[i],ans+1)]=arr[i];
}
}
}