度娘笔试归来,题目实打实的,感觉真心不易,上百号人就抢那么几个坑......只恨自己平时积累太少啊~
故曝一道鹅厂面试用的算法题(当时我就死在了这题上),来为度娘家攒一下RP~
题目:
对于长度为N的一个无序的数组a[1..N],请将a进行排序,要求所有正数都排在0之前,所有负数都排在0之后(如果没有0,则所有正数排在负数前)
要求时间复杂度O(N),空间复杂度O(1)
题目不难,但给思考的时间很短,大约不到5分钟吧。当时脑子比较短路,于是只给出了O(n) O(n)复杂度的算法,然后就被面试官挂掉了.....
更杯具的是,走出门就恍然大悟了~
solution:
这是一道类排序题,它并不要求严格的一一排序,难点在于如何将排序要求的降低转化为时间与空间的节省。
首先,起点是快排,O(N*logN)的时间复杂度,我们的算法需要比快排更快。O(N),即遍历一遍数组即完成排序。
很容易想到的是,题目只分正数/0/负数,如果在遍历数组的时候,将遇到的正数都塞到数组的头部,负数都塞道数组的尾部,即能在遍历一遍的情况完成。
问题是,在数组结构中,将某个数塞至头部/尾部,需要一位一位的移位,这意味着每次移位都要O(N)/2的时间复杂度,这是不能忍的!
那么如何做,才能保证O(1)的时间复杂度内完成移位呢?
当时,我想到的是新建一个空数组b[1..N],
定义两根指针i,j,i<-1,j<-N。
然后遍历数组A,
如果A[k]>0,则b[i]=A[k],i++;
反之,则b[j]=A[k],j--。
这样的做法达到了O(N)的时间复杂度要求,但新建的数组b则超过O(1)的空间复杂度。事实上,面试官一开始并没有要求空间复杂度。
这样的话,只能在原数组进行操作了,移位又太慢,就只能交换了。交换的话,问题在于和哪一位上的元素进行交换?
我们不妨用数学归纳法来表征这个问题:
假设在遍历到第m位的时候,a[1..m]能满足“正数<<0<<负数”,如何操作a[m+1],使得a[m+1]也满足上述条件呢?
如果a[m+1]=负数,那么我们就不用动它;
如果a[m+1]=0,我们就将它与排在最前面的负数交换;
如果a[m+1]=正数,会比较麻烦,我们要将它与最在最前的0交换,然后就将问题转化成了a[m+1]=0的情况。
综上,我们也需要两根指针i,j。i<-连续的正数序列中最后的那位,j<-连续的0序列中最后的那位。并在m++后,更新i,j即可。
伪代码如下:
int a[N]; a=random(N); int m=0; while(a[m]<=0) m++; swap(a[0],a[m]); int i=0,j=0; for(int m=1;m<N;m++) if(a[m]==0){ swap(a[m],a[j+1]); j++; }else if(a[m]>0) { swap(a[m],a[i+1]); i++; swap(a[m],a[j+1]); j++; }