同样,也是在头条里碰到的题目之一。
一天,小凯同学震惊的发现,自己屋内的PM2.5指标是有规律的!小凯采样了PM2.5数值,发现PM2.5数值以小时为周期循环,即任意时刻的PM2.5总是和一个小时前相等!他的室友小文同学提出了这样一个问题,在t小时内的所有采样点中国,选区若干采样点的数值,能否找到一个PM2.5不曾下降过的序列?这个序列最长是多少?
输入:
第一行有两个整数n和t,表示每小时的采样点个数,和询问多少个时间的结果
第二行有n个整数,以空格分隔,表示一个小时内,每个采样点观测到的PM2.5数值
很明显的一题求最长不下降子序列的题目。
优秀的O(nlogn)算法如下:
建立一个数组b[],其中b[i]表示长度为i的不下降序列中结尾元素的最小值,用len表示数组当前最长长度,算法完成后k的值即为最长不下降子序列的长度
假设,当前已求出长度为k,下一步判断a[i]和b[k]
- 如果a[i]>=b[k],即a[i]大于长度为k的序列中的最后一个元素,因此,长度可加1,b[k++]=a[i];
- 如果a[i]<b[k],则在0-k中找出第一个大于a[i]的数,并用a[i]替换之,很明显,用a[i]可以找到更长的长度
- 其中,寻找第一个大于a[i]的数可用二分查找,复杂度为O(logn),故整体的算法复杂度为O(nlogn)
vector<int> LLPM(n, 0); //初始化 //PM为原数组
LLPM[0] = PM[0];
int len = 1;
for( int i=1; i<n; ++i ){
int j = i;
if( PM[j] >= LLPM[len-1] ){ //当下一位大于LLPM[len-1],则在后面添加一位
LLPM[len++]=PM[j];
}
else{ //若下一位小于,则找到前len个中第一个比它大的数,并替换掉
auto k=upper_bound(LLPM.begin(), LLPM.begin()+len, PM[j]); //二分查找函数,找到第一个比它大的数
*k = PM[j];
}
}
输入参考例子
9 3 7 5 9 3 7 5 9 3 7 5
9 | 3 | 7 | 5 | 9 | 3 | 7 | 5 | 9 | 3 | 7 | 5 | |
a[0] | 9 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 |
a[1] | 0 | 0 | 7 | 5 | 5 | 3 | 3 | 3 | 3 | 3 | 3 | 3 |
a[2] | 0 | 0 | 0 | 0 | 9 | 9 | 7 | 5 | 5 | 3 | 3 | 3 |
a[3] | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 9 | 9 | 7 | 5 |
a[4] | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
由此可得出,最长不下降长度为4
以下是全部代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main(){
int n, t; //采样点n,小时t
cin >> n >> t;
vector<int> PM(n);
for(int i=0; i<n; ++i){
cin >> PM[i];
}
//寻找最长子串
vector<int> LLPM(n*t, 0); //初始化
LLPM[0] = PM[0];
int len = 1;
for( int i=1; i<n*t; ++i ){
int j = i%n;
if( PM[j] >= LLPM[len-1] ){ //当下一位大于LLPM[len-1],则在后面添加一位
LLPM[len++]=PM[j];
cout << "将" << PM[j] << "加到第" << len << "位 ";
for( int i=0; i<n*t; ++i )
cout << LLPM[i] << " ";
cout << endl;
}
else{ //若下一位小于,则找到前len个中第一个比它大的数,并替换掉
auto k=upper_bound(LLPM.begin(), LLPM.begin()+len, PM[j]); //二分查找函数,找到第一个比它大的数
*k = PM[j];
cout << "用" << PM[j] << "替换第" << k-LLPM.begin() << "位"<< " ";
for( int i=0; i<n*t; ++i )
cout << LLPM[i] << " ";
cout << endl;
}
}
cout << "长度为:" << len << endl;
return 0;
}
4 3
9 3 7 5
用3替换第0位 3 0 0 0 0 0 0 0 0 0 0 0
将7加到第2位 3 7 0 0 0 0 0 0 0 0 0 0
用5替换第1位 3 5 0 0 0 0 0 0 0 0 0 0
将9加到第3位 3 5 9 0 0 0 0 0 0 0 0 0
用3替换第1位 3 3 9 0 0 0 0 0 0 0 0 0
用7替换第2位 3 3 7 0 0 0 0 0 0 0 0 0
用5替换第2位 3 3 5 0 0 0 0 0 0 0 0 0
将9加到第4位 3 3 5 9 0 0 0 0 0 0 0 0
用3替换第2位 3 3 3 9 0 0 0 0 0 0 0 0
用7替换第3位 3 3 3 7 0 0 0 0 0 0 0 0
用5替换第3位 3 3 3 5 0 0 0 0 0 0 0 0
长度为:4
结果刚好验证了以上思想。最长不下降子序列打卡!
此外,对于最长递增、最长递减、最长不增加等子序列,只需要 修改其中的判断条件即可。