也谈最长不降子序列的长度
题目是一本通网站(ybt.ssoier.cn:8088)中1259:【例9.3】求最长不下降序列
【题目描述】
设有由n(1≤n≤200)n(1≤n≤200)个不相同的整数组成的数列,
记为:b(1)、b(2)、……、b(n)b(1)、b(2)、……、b(n)且b(i)≠b(j)(i≠j)b(i)≠b(j)(i≠j),
若存在i1<i2<i3<…<iei1<i2<i3<…<ie且有b(i1)<b(i2)<…<b(ie)b(i1)<b(i2)<…<b(ie)
则称为长度为e的不下降序列。程序要求,当原数列出之后,求出最长的不下降序列。
例如13,7,9,16,38,24,37,18,44,19,21,22,63,15。
例中13,16,18,19,21,22,63就是一个长度为7的不下降序列,
同时也有7 ,9,16,18,19,21,22,63组成的长度为8的不下降序列。
【输入】
第一行为n,第二行为用空格隔开的n个整数。
【输出】
第一行为输出最大个数max(形式见样例);
第二行为max个整数形成的不下降序列,答案可能不唯一,输出一种就可以了,本题进行特殊评测。
【输入样例】
14
13 7 9 16 38 24 37 18 44 19 21 22 63 15
【输出样例】
max=8 7 9 16 18 19 21 22 63
其实这个题很多的网文都说到了,我也看了好久才看懂,然后把我的理解发
出来大家下,希望还没看懂别的博文的朋友这次能让你懂。在众多的网文中,
https://blog.csdn.net/sdz20172133/article/details/79265834
这位兄弟pxlsdz说的算比较详细的了,感谢你。
输入的序号 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
输入的值 |
13 |
7 |
9 |
16 |
38 |
24 |
37 |
18 |
44 |
19 |
21 |
22 |
63 |
15 |
不降序列的长度 |
1 |
1 |
2 |
3 |
4 |
4 |
5 |
4 |
6 |
5 |
6 |
7 |
8 |
3 |
我们可以用数组来记录不降序列的长度一个一个数字的解释吧。第一个数13,
当然长度是1,就一个数嘛。第2个数7的长度也是1,共就两个数,7比13小,不
能形成长度为2的不降序列,就是1了。实际上这个时候可以理解为有两个序列了,
一个是13开头,一个是7开头。第3个数9,它可以接在7后面,不能接在13后面,
当然没必须再开一个新序列(不是最长嘛),所以是2。第4个数16可以接在13的
后面,那13的长度可以为2,但如果16接在9的后面,长度那形成的序列是7,9,
16,长度为3,显然应接在9后面,第5个数38接在16的后面,长度为4。这个时候
有一个长度为4的序列7,9,16,38。第6个数24,它不能接在38后面,但可以
接在16的后面,也就是如果要把24加入一个最长序列的话,那只是不要38了,
这将形成一个序列7,9,16,24,长度为4。第7个数37当然可以接在24的后面,
形成一个度为5的序列。(如果第7个数不是37,而是38,那39可以接在38后面,
也可以接在24的后面,那长度都一样)。第8个数18来了,它只能接在16的后面,
形成一个长度为4的序列。第9个数可以接在38、24、37、18的任何一个数的后
面,那接在谁的后面能获得最大长度呢?当然是37了,可以得长7,9,16,24,
37,44的序列,长度相比而言应该是最长的了。所以44的长度是6。接着19,21,
22当然顺次接到18的后面就好,所以长度分别为5,6,7。63应该可以接到44的
后面,长度为7,也可以接到22的后面,长度为8,那当然得往大的方面走了,所
以63的长度定为8。最后一个15只能接到9的后面,长度自然是3。每一个数的序
列长度实际上是把它与前面的数比较,找到比它小对应长度又最大的数就好(从
后面往前面找,长度也就是找数的升序+1)打擂记录最大长度就得到答案了。如
果要找出最长子序列,那就需要记录每一个数的前一个数了,当然记序号也可。
//求最长不降子序列长度,一本通网站1259题 #include<iostream> using namespace std; int a[201],b[201],c[201],mx=1,mxn; void pr(int x) { if(b[x]>1)pr(c[x]); cout<<a[x]<<' '; return; } int main(){ int n; cin>>n; for(int i=1;i<=n;i++) { cin>>a[i]; b[i]=1; } mxn=n;//如果最长不降子序列只有一个元素就输出最后一个元素 for(int i=2;i<=n;i++) for(int j=i-1;j>=1;j--) if(a[i]>=a[j]&&b[j]>=b[i]) { b[i]=b[j]+1; if(mx<=b[i])mx=b[i],mxn=i; c[i]=j; } cout<<"max="<<mx<<endl; if(mx>1)pr(c[mxn]);//本可以直接pr(mxn),但最后会多一个空格 cout<<a[mxn];//为避免最后多一个空格,最后一个元素单独输出 return 0; }
这种方法的时间复杂度是O(n^2)级,如果n较大,容易超时,可以用二分法搜索回以优化,可以把时间复杂度做到O(nlogn),现提供“之江学院石”的朋友的代码
#include<bits/stdc++.h> using namespace std; int a[201], b[201], mx = 1, mxn, n; int main() { cin >> n >> a[0]; b[0] = a[0]; for(int i = 1; i < n; i++) { cin >> a[i]; int j = upper_bound(b, b+mx, a[i]) - b; if(j == mx) mx++, mxn = i; b[j] = a[i]; } for(int i = mx-2, j = mxn-1; i >= 0; i--) { for(; a[j] < b[i] || a[j] > b[i+1]; j--); b[i] = a[j--]; } printf("max=%d\n", mx); for(int i = 0; i < mx; i++) printf("%d%c", b[i], i == mx - 1 ? '\n' : ' '); }
如有不妥之处,请指教!