LIS(最长上升子序列)(dp/二分)

写在前边:
如果需要记录路径,在每一次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];
			}		
		}
	}
发布了73 篇原创文章 · 获赞 3 · 访问量 3520

猜你喜欢

转载自blog.csdn.net/qq_42021845/article/details/102730846