1.实践题目:
两个有序序列的中位数
2.问题描述:
已知有两个等长的非降序序列S1, S2, 设计函数求S1与S2并集的中位数。有序序列A0,A1,⋯,AN−1的中位数指A(N−1)/2的值,即第⌊(N+1)/2⌋个数(A0为第1个数)。
输入格式:
输入分三行。第一行给出序列的公共长度N(0<N≤100000),随后每行输入一个序列的信息,即N个非降序排列的整数。数字用空格间隔。
输出格式:
在一行中输出两个输入序列的并集序列的中位数。
3.算法描述:
#include<iostream> using namespace std; const int maxn = 2e5+5; int a[maxn]; int n,x,cnt=1; int main() { cin>>n; for(int i=1;i<=2*n;i++) cin>>a[i]; int cnt1=1,cnt2=n+1; for(;cnt<n+1;cnt++){ if (a[cnt1]<a[cnt2]) x=a[cnt1++]; else x=a[cnt2++]; } cout<<x<<endl; }
开两个数组存两个序列,然后用两个指针从头按大小一路扫过去并用,cnt和x记录新序列的位置和值,由于没有去重,那么新序列的中位数就是指针扫到n时的值。
4.算法时间及空间复杂度分析:
由于是线性的直接遍历一遍,所以时间复杂度是O(N)的;
空间复杂度上,我开了1个2e5+5的数组,所以空间复杂度是O(2e5);
5.心得体会
这题虽然说是并集但其实不用去重,去重的话你最后一个样例是过不了的,因为这个原因我debug了大半小时。
其实这题还有一个log的算法,大概的思路是先在两个序列里找到N/2的子序列,判断子序列的最大值(由于是有序的,这个可以O(1)达到)把小的那个序列扔出去,并且维护扔出去的元素的最大值,然后在从小到大找到max(N/4,1)的子序列,再一次把小的那个子序列扔出去,维护扔出去的元素的最大值(包含之前扔出去的元素),继续对接下来的序列进行同样的操作(长度变为max(N/8,1)).......直至扔出的序列长度达到N,此时扔出去的最大值就是所要求的中位数,这个时间复杂度度是log级别的,因为每次都进行了二分的操作(寻找子序列的位置的时候由于是有序的所以是O(1)的),这个由于一开始没想出来所以我也没有敲代码,不过思路上来讲挺好实现的。
想了一下还是补充一下log的代码吧(没有特意出测试来调,但应该没错)
#include<bits/stdc++.h> using namespace std; const int maxn =1e5+5; int a[maxn]; int b[maxn]; int main() { int n,mid1=0,mid2=0; cin>>n; for(int i=1;i<=n;i++) cin>>a[i]; for(int i=1;i<=n;i++) cin>>b[i]; int r1=n,r2=n,l1=0,l2=0,len=n,_max=0,l=0; while(l!=n) { len=max(len/2,1); mid1=l1+len; mid2=l2+len; // cout<<len<<" "<<mid1<<" "<<mid2<<endl; if(a[mid1]<b[mid2]) { _max=max(_max,a[mid1]); l1=mid1; // cout<<_max<<" A"<<endl; } else { _max=max(_max,b[mid2]); l2=mid2; // cout<<_max<<" B"<<endl; } l+=len; } cout<<_max<<endl; }