给定一个长度为N的数列,求数值严格单调递增的子序列的长度最长是多少。
输入格式
第一行包含整数N。
第二行包含N个整数,表示完整序列。
输出格式
输出一个整数,表示最大长度。
数据范围
1≤N≤1000,
−109≤数列中的数≤109
输入样例:
7
3 1 2 1 8 5 6
输出样例:
4
上图
这里的话就开个一维数组,f[i]表示所有以第i个数结尾上升子序列
代码:
# include <iostream>
# include <cmath>
using namespace std;
const int N = 1000;
int a[N+10];
int dp[N+10];
int main(void)
{
int n;
cin>>n;
int ans=0;
for(int i=1;i<=n;++i)
cin>>a[i];
for(int i=1;i<=n;++i){
dp[i]=1;
for(int j=1;j<i;++j){ //循环遍历【i,j】之间的dp[]值
if(a[j]<a[i])
dp[i]=max(dp[i],dp[j]+1);
}
ans=max(ans,dp[i]);
}
cout<<ans<<endl;
/*
//这个是求出最长上升子序列的代码
int idx = -1;
for(int i=1;i<=n;++i){ //先找出最长上升子序列的最后一个的下标
if(dp[i]==ans){
idx = i;
break;
}
}
int len = ans;
int b[10000]={0};
b[len--] = a[idx];
for(int i=idx;i>1;--i){ //以这个为边界,进行向前逆向解答即可。
if(a[i-1]<a[idx] && dp[i-1]==dp[idx]-1){ //因为答案肯定在左边的序列中
b[len--] = a[i-1];
idx = i-1;
}
}
for(int i=1;i<=ans;++i){
cout<<b[i]<<' ';
}cout<<endl;
*/
return 0;
}
这里的话是DP做法,时间复杂度对于1000还可以,那么问题来了,如果是100000怎么做?(用贪心算法)
思路:
f[i]数组表示长度为i的上升子序列,这里的话用下标表示,也就是说每个值都是唯一的,比如下标为3的就表示长度为3的上升子序列,有且仅仅保留一种最佳的长度为3的上升子序列;
f[]数组存的是每个长度上升子序列的最后一个值,即i等于3的话,f[3]存的就是长度为3的最佳上升子序列的末尾的元素
首先数组a中存输入的数(原本的数),开辟一个数组f用来存结果,最终数组f的长度就是最终的答案;假如数组f现在存了数,当到了数组a的第i个位置时,首先判断a[i] > f[cnt] ? 若是大于则直接将这个数添加到数组f中,即f[++cnt] = a[i];这个操作时显然的。
当a[i] <= f[cnt] 的时,我们就用a[i]去替代数组f中的第一个大于等于a[i]的数,因为在整个过程中我们维护的数组f 是一个递增的数组,所以我们可以用二分查找在 logn 的时间复杂的的情况下直接找到对应的位置,然后替换,即f[l] = a[i]。
我们用a[i]去替代f[i]的含义是:以a[i]为最后一个数的严格单调递增序列,这个序列中数的个数为l个。
这样当我们遍历完整个数组a后就可以得到最终的结果。
时间复杂度分析:O(nlogn)
代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 1e5 + 5;
int a[maxn], f[maxn];
int cnt;
inline int find(int x) {
int l = 1, r = cnt;
while(l < r) {
int mid = l + r >> 1;
if(f[mid] >= x) r = mid;
else l = mid + 1;
}
return l;
}
int main(void) {
int n;
scanf("%d", &n);
for(int i = 1; i <= n; i ++) scanf("%d", &a[i]);
f[++cnt] = a[1];
for(int i = 2; i <= n; i ++)
if(a[i] > f[cnt]) f[ ++ cnt] = a[i]; //如果大于数组末尾元素,那么表示现在最长上升子序列应该是
else { //cnt+=1
int tmp = find(a[i]); //否则就二分,然后更新末尾元素值
f[tmp] = a[i];
}
printf("%d\n", cnt);
return 0;
}
求出最长的上升子序列:
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 1e5 + 5;
int dp[maxn];
int a[maxn], f[maxn];
int b[maxn];
int cnt;
inline int find(int x) {
int l = 1, r = cnt;
while(l < r) {
int mid = l + r >> 1;
if(f[mid] >= x) r = mid;
else l = mid + 1;
}
return l;
}
int main(void) {
int n;
scanf("%d", &n);
for(int i = 1; i <= n; i ++) scanf("%d", &a[i]);
f[++cnt] = a[1];
dp[1] = 1;
for(int i = 2; i <= n; i ++)
if(a[i] > f[cnt]) {
f[ ++ cnt] = a[i];
dp[i] = cnt;
} //如果大于数组末尾元素,那么表示现在最长上升子序列应该是
else { //cnt+=1
int tmp = find(a[i]); //否则就二分,然后更新末尾元素值
f[tmp] = a[i];
dp[i] = tmp;
}
printf("%d\n", cnt);
int ans = cnt;
int idx = -1;
for(int i=1;i<=n;++i){
if(dp[i]==ans){
idx = i;
break;
}
}
int len = ans;
int b[10000]={0};
b[len--] = a[idx];
for(int i=idx;i>1;--i){
if(a[i-1]<a[idx] && dp[i-1]==dp[idx]-1){
b[len--] = a[i-1];
idx = i-1;
}
}
for(int i=1;i<=ans;++i){
cout<<b[i]<<' ';
}cout<<endl;
return 0;
}