给定数组arr,设长度为n,输出arr的最长递增子序列。(如果有多个答案,请输出其中字典序最小的)
输出两行,第一行包括一个正整数n(n<=100000),代表数组长度。第二行包括n个整数,代表数组arr \left(1 \leq arr_i \leq 1e9 \right)(1≤arri≤1e9)。
输出一行。代表你求出的最长的递增子序列。
示例1
复制
9 2 1 5 3 6 4 8 9 7
复制
1 3 4 8 9
示例2
复制
5 1 2 8 6 4
复制
1 2 4
其最长递增子序列有3个,(1,2,8)、(1,2,6)、(1,2,4)其中第三个字典序最小,故答案为(1,2,4)
最原始的最长递增子序列问题只需要求出最长递增子序列的长度即可,为了能够打印出整个递增子序列,需要额外加一个数组保存序列中的前一个节点。以这个数组为例:
7,1,8,2,9,10,3,1,11
字典序最小的最长递增子序列为:1,2,9,10,11。
首先是传统的O(n.^2)复杂度的解法,dp[i]表示以下标i结尾的最长递增子序列的长度。那么整个遍历过程如下:
1. i=7(下标0),dp[0]=1
2. i=1(下标1),此时遍历i之前的所有元素,没有比i更小的元素,所以dp[1]=1
3.i=8(下标2),此时遍历i之前所有元素:首先j=7,7与8可以组成长度为2的递增子序列,所以更新dp[2]=2,由于最后还需要打印出整个递增子序列,所以需要存储8之前的元素是7,所以新开一个数组pre,pre[2]=0,表示下标为2的元素之前是7(下标0)。继续增加j=1,此时我们发现1与8组成递增子序列长度也为2,并且如果1作为8的前一个元素,字典序会更小,所以更新pre[2]=1(下标1)
4.i=2,遍历之前所有的元素,1与2可以组成长度为2的递增子序列2,更新dp[3]=2,pre[3]=1。
5. i=9,遍历之前所有的元素,以2结尾的递增子序列长度为2,加上9可以构成长度为3的递增子序列,而9之前的其余元素无法和9构成长度为3的递增子序列,所以更新dp[4]=3,pre[4]=3(下标3,即2)
以此类推,遍历完之后dp的最大值对应下标即是字典序最小的最长递增子序列结尾元素,然后按照pre数组回溯即可找到整个序列。
接下来就是O(nlogn)复杂度的解法了,核心思想也是构建一个pre数组。和“打印最长递增子序列长度”问题一样,利用二分查找。
构造两个数组len和pre,pre的语义同上,len[i]=j表示长度为i的递增子序列最后一个元素为j
遍历过程为:
1.i=7,len为空,len[1]=7,pre[7]=0(7的前一个元素是下标0)
2.i=1 ,在len中通过二分查找查询大于等于1的第一个元素,更新len[1]=1(此时,长度为1的递增子序列最后一个元素由7变成了1,字典序更小)(1的前一个元素是下标1)
3.i=8,比len最后一个元素1还大,直接把i push到len当中,len(2)=8,8的上一个元素是len倒数第二个元素1,所以pre[8]=1(8的前一个元素是下标1)
4. i=2,在len中查找大于等于2的第一个元素,是8,更新len[2]=2,此时2的上一个元素是len倒数第二个元素1,所以pre[2]=1。
5. i=9 ,比len最后一个元素还要大,直接把9 push到len中,len[3]=9,9的上一个元素是len倒数第二个元素2,所以pre[9]=3(9的上一个元素是下标3)
以此类推,到最后,len最后一个元素即为字典序最小的最长递增子序列最后一个元素,根据pre即可回溯找到序列中的所有元素。
ps:上述例子中为了方便理解,pre数组和len数组里面存储的是数组元素的值,实际编程的时候应该存储数组元素的下标。例如第一步里面,len[1]=7应该改成len[1]=(7所对应的下标)。
代码如下:
#include
using namespace std;
int n;
vector arr;
void solve();
int main(){
scanf("%d",&n);
arr.resize(n);
for(int i=0;i len;//递增子数组的长度
void solve(){
vector pre;
pre.resize(n);
len.reserve(n);
len.push_back(0);//len[0]没有实际意义
len.push_back(0);//初始情况下,len[1]=0,表示长度为1的递增序列以arr[0]结尾
pre[0]=INT_MIN;//INT_MIN表示没有上一个元素了
for(int i=1;iarr[len[len.size()-1]]){
len.push_back(i);
pre[i]=len[len.size()-2];
//cout<<"1:pre[i]="< result;
result.reserve(n);
int start=len[len.size()-1];
while(start!=INT_MIN){
result.push_back(arr[start]);
start=pre[start];
}
int result_len=(int)result.size();
for(int i=result_len-1;i>0;i--) printf("%d ",result[i]);
printf("%d\n",result[0]);
}
//[l,r] binary search
int binary_search(int l,int r,int target){
int left=l,right=r;
int result=-1;
while(l<=r){
int mid=(l+r)>>1;
if(arr[len[mid]]