2013年的创新工场笔试考了:http://blog.csdn.net/huangxy10/article/details/8026464
而且应该还是一道经典的笔试面试题:http://fayaa.com/tiku/view/2/
在上面链接中,有人给出如下几种方法:
对于丢失一个数的情况:
1)用1+2+...+n减去当前输入数据的总和。时间复杂度:O(n) 空间复杂度:O(1) 【容易溢出】
2)用12...*n除以当前输入数据的总积。时间复杂度:O(n) 空间复杂度:O(1) 【容易溢出】
3)用1^2^...^n的结果在逐个异或当前输入数据。时间复杂度:O(n) 空间复杂度:O(1)
4)对输入数据排序,然后从头到尾遍历一次。时间复杂度O(nlogn) 空间复杂度O(1)
5) 对输入数据进行Hash,然后从头到尾遍历一次。时间复杂度O(n) 空间复杂度O(n)
第三种方法,个人认为是最为精妙的,因为相同的数取异或为0,0^0为0,所以最终按照该方法得到的数就是缺失的那个数。其算法复杂度准确的说为:Theta(2*n-1)。方法1和2,虽然简单,且时间复杂度和空间复杂度都不错,但可能因为n过大,存在溢出问题。4空间复杂度好,但是需要排序后再找,即使使用计数排序这种O(n)的排序方法(其实对于这道题,还是挺适合计数排序的),复杂度也是O(n)的样子。方法5使用哈希算一遍要theta(n),再遍历一遍,其实也是Theta(2*n-1)。
后三种都能解决问题,第三种较好。
经过思考,我也给出了另外一种Theta(2*n-1)的算法(哎,本来以为是Theta((lgn)^2)的,不过算了一下还是Theta(2*n-1)的):
对于从1~n的数,减少了一个数,存在数组a[n-1]中,并且顺序是乱的。
核心思想就是比较中位数位置和中位数元素的关系。基本思想是这样的,首先找到数组a[n-1]的中位数,如果发现中位数与该位置是相等的,则说明中位数放在了正确的位置,那么缺失的数肯定是要大于该中位数的(否则,中位数元素会比该位置大1),则缺失的数在中位数右侧。如果不相等的话,则缺失的数一定在中位数左边。确定了范围之后,再继续这样递归查找,直到查找结束,其实这个算法有点像二分法和中位数查找的结合。这样说可能不太明白,举个例子:
例如,现在是1~10这10个数,a[9]={5,8,6,2,3,4,1,9,10};缺的是7这个数,那么,这个数组如果排序的话应该是这样的:
{1,2,3,4,5,6,8,9,10},p=1,q=9,中位数位置r=(p+q)/2=5,而中位数元素是"5",二者相等,说明5在正确的位置,缺失的数一定比5大。注意,这时候算法已经将5摆在正确的位置。继续在大于5的数的序列中按此方法查找。
{6,8,9,10} ,p=6,q=9,中位数位置r=(p+q)/2=7,而此时找到的中位数的元素是a[7]=8,二者不等,说明已经发现缺失的数,比8小。继续在左侧查找
这时候只剩下{6},p=6,q=6,r=(p+q)/2=6,则发现a[6]=6,是相等的。缺失的数在哪?事实上,对于在正好结束时,如果存在中位数和中位数元素相等的时候,那么其实缺失的数就是最终结束的中位数右侧的数,即比该数大1的数。所以递归最后结束的条件是这样的。
if(p==q) { if(a[p]==p) return p+1; //如果当p=q查找结束的时候,发现a[p]与p相等,其实说明缺失的数正好是该数右边的数 else return p; //如果不相等,则说明缺失的数正好是终止的位置的数(事实上,在这里a[p]>p) }
#include <stdlib.h> #include <stdio.h> #define N 10 #define NL 8 // NL为缺失的数 void Swap(int *a, int *b){ int tmp; tmp=*a; *a=*b; *b=tmp; } //快排中的划分函数 int Partition(int *a, int p, int q){ int i,j,x=a[q]; for(i=p-1,j=p;j<q;j++){ if(a[j]<x){ Swap(&a[j],&a[++i]); } } Swap(&a[j],&a[++i]); return i; } //找到从小到大排,排第pos个的数的值,在该算法中是找中位数的值 int FindPos(int *a, int p, int q, int pos){ if(p==q) return a[p]; int r=Partition(a,p,q); if(r<pos) return FindPos(a,r+1,q,pos-r); else if(r>pos) return FindPos(a,p,r-1,pos); else return a[r]; } //查找1-n中缺失的数 int FindLost(int *a, int p, int q){ if(p==q) { if(a[p]==p) return p+1; //如果当p=q查找结束的时候,发现a[p]与p相等,其实说明缺失的数正好是该数右边的数 else return p; //如果不相等,则说明缺失的数正好是终止的位置的数(事实上,在这里a[p]>p) } int r=(p+q)/2; //中位数位置 int realnum=FindPos(a,p,q,r); //找到中位数位置实际的值 if(realnum == r) return FindLost(a,r+1,q); //如果找到的真实的数与当前位置吻合,那么缺失的数一定在右侧 else return FindLost(a,p,r-1); //否则在左侧找; } int main(){ int i,j; int a[N]={-1,5,8,6,2,3,4,1,9,10}; for(i=1;i<N;i++){ printf("%d ",FindPos(a,1,N,i)); } printf("\n"); printf("缺失的数是%d ",FindLost(a,1,N-1)); system("pause"); return 0; }
时间复杂度:FindPos实际就是算法导论中给出的中位数查找(查找第n个最小值问题)的算法,其复杂度是theta(n)。之后,每次折半查找,算法复杂度=theta(n)+theta(n/2)+
theta(n/4)+……+theta(1)=theta(n*(1+1/2+1/4+1/lgn))=theta(n*(2-(1/2)^lgn))=theta(2n-1)(哎,折腾了半天,还是2n-1的),空间复杂度是O(1)。