最长递增子序列(LIS)时间复杂度详解

问题描述

所谓最长递增子序列,就是从一个数组中,从左至右选择若干个数,使得组成的新序列长度最长。

解题思路

1.转换成最长公共子序列问题

待更新~~~~~

2.普通动态规划(时间复杂度O(n^2))

普通的动态规划思路就是先初始化len[i]为1,然后遍历 下标为0~ i-1的所有元素,从而对len[i]进行更新;
代码如下:

void solve2(int num[],int l){
	int len[100];
	memset(len,0,sizeof(len));
	int maxn=0;
	for(int i=0;i<l;i++){
		len[i]=1;
		for(int j=0;j<i;j++){
			if(num[i]>num[j]&&len[i]<len[j]+1)
				len[i]=len[j]+1;
		}
		maxn=max(maxn,len[i]);
	}
	cout<<"最长为 "<<maxn<<endl;
}

3.优化动态规划(时间复杂度O(n log n)

上述动态规划我们会发现,访问 0~i-1这段序列时有大量重复操作;因此可以考虑以下优化过的动态规划。

1.设数组B[len],用于记录长度为len的递增子序列的末尾元素;
2.当遇到 B[len]<num[i]时,就记录B[++len]=num[i],意味着长度为len+1的递增序列以num[i]结尾;
3.当B[len]>num[i]时,用二分查找找出B[]中能以num[i]结尾的最长len;

代码如下:

void solve3(int num[],int l){
	int B[100];//表示长度为 i 的序列结尾元素是B[i] 
	memset(B,0,sizeof(B));
	int len=1;
	for(int i=0;i<l;i++){
		if(B[len]<num[i]){
			B[len]=num[i];
			len+=1;
		} 
		else {
			int pos=serach_index(B,len,num[i]);
			B[pos]=num[i];
		}
	}
	for(int i=1;i<=len;i++){
		cout<<"长度为 "<<i<<" 的序列结尾元素是 "<<B[i]<<endl;
	}
}

完整代码如下

#include<bits/stdc++.h>
using namespace std;
/*
思路:
	
*/
int num[100];
void solve2(int num[],int l){
	int len[100];
	memset(len,0,sizeof(len));
	int maxn=0;
	for(int i=0;i<l;i++){
		len[i]=1;
		for(int j=0;j<i;j++){
			if(num[i]>num[j]&&len[i]<len[j]+1)
				len[i]=len[j]+1;
		}
		maxn=max(maxn,len[i]);
	}
	cout<<"最长为 "<<maxn<<endl;
}
int serach_index(int B[],int len,int x){
	int lef=1,rig=len;//??
	while(lef<rig){
		int mid=(lef+rig)/2;
		if(B[mid]>=x){
			rig=mid;
		}
		else lef=mid+1;
	}
	return lef;
}
void solve3(int num[],int l){
	int B[100];//表示长度为 i 的序列结尾元素是B[i] 
	memset(B,0,sizeof(B));
	int len=1;B[1]=num[0];
	for(int i=0;i<l;i++){
		if(B[len]<num[i]){
			B[++len]=num[i];
		} 
		else {
			int pos=serach_index(B,len,num[i]);
			B[pos]=num[i];
		}
	}
	for(int i=1;i<=len;i++){
		cout<<"长度为 "<<i<<" 的序列结尾元素是 "<<B[i]<<endl;
	}
}

int main(){
	int n;
	while(cin>>n){
		for(int i=0;i<n;i++)
			cin>>num[i]; 
		solve2(num,n);
		solve3(num,n);
	}
   return 0;
   
}

问题拓展

我们已经求出来最长子序列的长度了,那么如何确定该序列的各个元素的值?

解题思路

1.贪心+二分

用一般的dp的时间复杂度是O(n^2);不推荐
对于用二分优化的方法,我们只需要用p[i]表示元素num[i]在B[]中对应的下标即可;
	num[i]>B[]     p[i]=len;
	else           p[i]=f;//f是num[i]在B[]中对应的下标

下面用具体样例解释:
对于长度为9的序列[2,1,5,3,6,4,8,9,7];
在这里插入图片描述
然后再遍历p[]

for(int i=l-1;i>=0;i--){
        	if(p[i]==len){
        		ans.push_back(arr[i]);
        		len--;
			}
		}

这种遍历能保证长到的序列长度最长且字典序最小;

完整代码

    int B[100050];
    int p[100050];
    int search_index(int len,int tar){
	int l = 1,r = len;
	while(l < r){
		int mid = (l + r) / 2;
		if(B[mid] >= tar)r = mid;
		else l = mid + 1;
	}
	return l;
}
    vector<int> LIS(vector<int>& arr) {
        // write code here
	    int len = 1;int l = arr.size();
        B[len] = arr[0];
	    for(int i = 0; i < l; i++){
		    if(B[len] < arr[i]){
	    		B[++len] = arr[i];
                p[i] = len;
	    		//++len;
	    	}
		    else {
		    	int pos = search_index(len,arr[i]);
                //int pos = lower_bound(B + 1,B + 1 +len, arr[i]) - B;
	    		B[pos] = arr[i];
                p[i] = pos;
	    	}
	    }
        vector<int> ans; ans.clear();
        for(int  i = l -1; i >= 0; i--){
            if(p[i] == len){
                ans.push_back(arr[i]);
                len--;
            }
        }
        reverse(ans.begin(), ans.end());
        return ans;
    }
};

例题

1.牛客-最长递增序列
2.leetcode-无矛盾的最佳球队

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

高冷小伙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值