原题链接:https://www.acwing.com/problem/content/5463/
连续整数序列指的是由若干个连续正整数组成的递增序列,例如 [1],[3,4,5],[7,8,9,10,11] 都是合法的递增序列。
给定一个长度为 n 的正整数序列 a1,a2,…,an,请你找到该序列的一个尽可能长的子序列,使得该子序列恰好是一个连续正整数序列。
注意,子序列不一定连续。
输入格式
第一行包含整数 n。
第二行包含 n个整数 a1,a2,…,an。
输出格式
第一行输出一个整数 k,表示满足条件的子序列的最大可能长度。
第二行输出 k 个整数,表示该子序列所包含的所有元素在原序列中的下标。
所有下标按照升序顺序输出。
如果答案不唯一,输出任意合理答案均可。
数据范围
前 5 个测试点满足 1≤n≤10
所有测试点满足 1≤n≤2×1e5,1≤ai≤1e9。
输入样例1:
7
3 3 4 7 5 6 8
输出样例1:
4
2 3 5 6
输入样例2:
6
1 3 5 2 4 6
输出样例2:
2
1 4
输入样例3:
4
10 9 8 7
输出样例3:
1
1
输入样例4:
9
6 7 8 3 4 5 9 10 11
输出样例4:
6
1 2 3 7 8 9
解题思路:
一看题目就是很明显的线性dp,直接定义f[i]表示以数值i结尾的连最长续递增序列的长度,然后f[i]=max(f[i],f[i-1]+1),正常情况下这样就解决了,但是这个题目有一个比较麻烦的地方就是a[i]的值域比较大,最大是1e9,我们肯定不能定义一个大小为1e9的数组,这个时候我们可以使用将dp数组f定义为哈希表类型,到这里题目还并没有解决完,题目还要求输出最长连续递增序列,也就是还要记录转移,为了方便我们定义一个数组g,定义g[i]表示以当前下标i处的值a[i]结尾的最长递增子序列是从哪个位置转移过来的,当前位置值为a[i],肯定是从值v=a[i]-1转移过来的,但是数组a中可能有多个a[i]-1,每个a[i]-1处的长度都可能不一样,所以我们的g[i]记录转移记录的是下标,而不是值,g数组记录转移下标是因为后面需要输出最长递增序列的下标,如果记录的是值可能会导致出现错误,这个是需要注意的一个点,例如如下例子[1,2,3,2,3,4,5],这里以a[6]=4结尾的最长递增序列的长度为4,但是4是从a[5]=3转移过来的,而不是从a[3]=3转移过来的,如果g数组记录的是转移的值,如果写法不当,很可能会导致我们将转移错误的写法从a[3]=3转移过来,就会导致答案错误,所以我们的g数组最好记录的是转移的下标,而不是值,而且我们f[a[i]-1]记录的以a[i]-1结尾的最长递增序列的长度,最长的a[i]可能在最长的a[i-1]前面,如果用值来记录转移,那么a[i]可能会转移到他后面的a[i]-1,这肯定是不对的,a[i]肯定只能从前面的a[i]-1转移过来,例如[2,3,4,5,1,2,3,4],这里的4有俩个,这里的5肯定是从前面的4转移过来的,肯定不可能从后面的4转移过来,但是后面的4记录的长度是4比前面4记录的长度为3要长,如果转移记录的是值,写法不当就会出现这种错误,到这里这个题目就分析的差不多了,具体分析见代码处。
时间复杂度:dp时间复杂度为O(n),n=2e5,这个时间是可以过的,注意这里的g数组也是可以使用哈希表记录的,但是哈希表比数组效率低,所以能使用数组就尽量使用数组,这样效率更高,对于一些时间卡的比较紧的题目,就需要使用到这种技巧来优化时间。
空间复杂度:使用一个g数组和一个哈希表f,空间复杂度为O(n)。
cpp代码如下:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <unordered_map>
using namespace std;
const int N=2e5+10;
int n;
int a[N];
unordered_map<int,pair<int,int>>f;
int g[N];
int main()
{
cin>>n;
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
//f[i]表示以值i结尾的最长连续递增序列的长度,g[i]记录的是从哪个下标转移过来的
for(int i=1;i<=n;i++)
{
int u=a[i],v=a[i]-1;
if(f.count(v)){ //如果a[i]-1在前面出现过
if(f.count(u)){ //如果a[i]在前面出现过
if(f[v].first+1>f[u].first){ //更新状态,并且记录转移
f[u]={f[v].first+1,i};
g[i]=f[v].second;
}
}else { //如果a[i]在前面没有出现过,直接更新f[a[i]]
f[u]={f[v].first+1,i};
g[i]=f[v].second;
}
}else { //如果a[i-1]在前面没有出现过,那么以当前a[i]结尾的最长长度就是1,自己是第一个数,自己转移到自己
f[u]={1,i};
g[i]=i;
}
}
int ans=0,v=0; //ans记录最长连续递增序列的长度,v记录的是下标
for(auto t:f){
if(t.second.first>ans){
ans=t.second.first;
v=t.second.second;
}
}
cout<<ans<<endl;
vector<int>nums;
while(true)
{
nums.push_back(v);
if(v==g[v])break; //当转移下标是自己时,说明已经到达答案序列的第一个数,跳出循环
v=g[v];
}
//是从结果往前找的答案序列,所以这里需要倒序输出
for(int i=nums.size()-1;i>=0;i--)
printf("%d ",nums[i]);
return 0;
}