问题描述
所谓最长递增子序列,就是从一个数组中,从左至右选择若干个数,使得组成的新序列长度最长。
解题思路
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;
}
};