题目是这样的, 数组是无序的, 可能没有重复的数,但最多只可能有一个重复的数,要求用最快的方法找到是否有重复的数。  



乍一想,挺难的,但是其实非常的简单。  



解决办法:  




数组a[N],1至N-1这N-1个数存放在a[N]中,其中某个数重复一次。写一个函数,找出被重复的数字。时间复杂度必须为o(N)函数原型:  



int do_dup(int a[],int N)  



××××××××××××××××××××××××××××××××××  


假金条的数学思想  



此算法题借鉴了假金条的思想,不过比那个过程简单,存放1至N-1,就相当于依次从各个袋中取出1至N-1个金条,但是有一个是重的,计算这N个数的和将相当于称总重量,减去1至N-1的和(用数学方法求)就可求出来重复的数。总之要充分利用数学知识  





const int N = 5; 


int a[N]={2,1,3,1,4}; 


int tmp1 = 0; 


int tmp2 = 0; 


for (int i=0; i<N; ++i) 


{ 


tmp1+=(i+1); 


tmp2+=a[i]; 


} 


printf("重复的数:%d\n",N-(tmp1-tmp2)); 




上述方法求1~N的和,减去数组总和,即为N-x 的差值,x为待找的数 


可以优化的是1-N的和不用程序算,数学方法直接算了 


可定义一个宏, 


#define sum(x) (x(x+1)/2) 


当然乘法操作是比较复杂的,当N较小时加几个数的效率可能更高 




×××××××××××××××××××××××××××××××××× 


标志数组法 




申请一个长度为n-1且均为'0'组成的字符串。然后从头遍历a[n]数组,取每个数组元素a[i]的值,将其对应的字符串中的相应位置置1,如果已经置过1的话,那么该数就是重复的数。就是用位图来实现的。 




其实,只要数还是0 -- n-1里面的数,那么可以用这样的方法判断所有的重复数的位置和值。 


比如这样的一个数组 


{2,3,1,2} 


我们生成一个字符串"000"; 


然后开始遍历,a[0] = 2; 


所以,字符串的第二位为"0",那么我们将其置为"1" 


字符串为"010" 


重复,字符串为"011",,,,,"111" 


然后,判断a[3] = 2 那么 第二位为"1" 


所以,a[3]为重复数,并且位置为第4位。 




上述map等各方法的思路是一致的,即访问过后做标记,再次访问时即可知道是否重复。如果考虑空间复杂度的话,其空间o(N) 


int do_dup(int arr[],int NUM) 


{ 


 int *arrayflag = malloc(NUM*sizeof(int)); 




 while(i++<NUM) 


 arrayflag[i] = false; 




 for(int i=0; i<NUM; i++) 


 { 


 if(arrayflag[arr[i]] >= false) 


 arrayflag[arr[i]] >= true; // 置出现标志 


 else 


 return arr[i]; //返回已经出现的值 


 } 


} 




×××××××××××××××××××××××××××××××××× 


固定偏移标志法 




利用标记法单纯写个O(N)的方法并不难,关键是如何保证两点: 


不改变A[]的初始值 


函数内不开辟另外的O(N)内存空间. 




很明显上述方法申请了O(N)内存空间,当N过大时,性能降低了 


因此应利用a[N]本身中值和下标的关系来做标记,处理完成后再清除标记即可 




a[N],里面是1至N-1。原数组a[i]最大是N-1,若a[i]=K在某处出现后,将a[K]加一次N,做标记,当某处a[i]=K再次成立时,查看a[K]即可知道K已经出现过。a[i]在程序中最大也只是N-1+N=2N-1(溢出了怎么办啊???),此为一个限制条件 




int do_dup(int arr[],int NUM) 


{ 


int temp=0; 




for(int i=0; i<NUM; i++) 


{ 


 if(arr[i]>=NUM) 


 temp=arr[i]-NUM; // 该值重复了,因为曾经加过一次了 


 else 


 temp=arr[i]; 




 if(arr[temp]<NUM) 


 { 


 arr[temp]+=NUM; //做上标记 


 } 


 else 


 { 


 printf("有重复"); 


 return temp; 


 } 


} 




printf("无重复"); 


return -1; 


} 


上面就是时间复杂度O(N), 空间复杂度O(1)的算法! 




×××××××××××××××××××××××××××××××××× 


符号标志法 




上述方法将元素加一个固定的NUM来做标记,可能会造成溢出。下列方法中利用符号位来做标记,因为1~N都为正值,所以就无限制了。基本思想是用int数组的符号位作哈希表。(反正不是unsigned int符号位闲着也是闲着) 




bool dup(int array[],int n) 


{ 


 for(int i=0;i<n;i++) 


 { 


 if(array[i]>0) //可以以此作下标去判断其他值 


 { 


 if(array[array[i]]<0) 


 { 


 return array[i];//已经被标上负值了,有重复 


 } 


 else 


 { 


 array[array[i]]= -array[array[i]]; //否则标记为负 


 } 


 } 


 else // |array[i]|代表的值已经出现过一次了 


 { 


 if(array[-array[i]]<0) 


 { 


 return -array[i];//有重复 


 } 


 else 


 { 


 array[-array[i]]=-array[-array[i]]; 


 } 


 } 


 } 


 return -1;//没有重复 


} 




//用于修复数组 


void restorearray(int array[],int n) 


{ 


 for(int i=0;i<n;i++) 


 { 


 if(array[i]<0)array[i]= -array[i]; 


 } 


} 


时间复杂度o(n) 空间复杂度o(1) 


不过时间复杂度o(n) 空间复杂度o(1)不只一个重复利用此法也是可以实现的 






//附上我修改后的算法 

bool do_dup_mal(int arr[], int n, int *pre, int *back) 

{ 

int MAX = -1; 

int i = 0; 

int sameVal = -1; 

assert(pre && back); 

*pre = *back = -1; 


for (int j=0; j<n; j++) 

{ 

if (arr[j] > MAX) MAX = arr[j]; 

} 


char *arrayflag = new char[MAX+1] ; 

if (NULL == arrayflag) 

return -1; 

//while(i++ < MAX) arrayflag[i] = '\0'; 

memset(arrayflag, 0, MAX+1 ); // '\0' == 0 

for(i=0; i<n; i++) 

{ 

if(arrayflag[arr[i]] == '\0') 

arrayflag[arr[i]] = '\1'; // 置出现标志 

else 

{ 

sameVal = arr[i]; //返回已经出现的值 

*back = i; 

break; 

} 

} 

delete[] arrayflag; 

if (i < n) 

{ 

for (int j=0; j<n; j++) 

{ 

if (sameVal == arr[j]) 

{ 

*pre = j; 

return true; 

} 

} 

} 

return false; 

} 



int main(int argc, char *argv[]) 

{ 

int prePos = -1, backPos = -1; 

int myArry[N]; 

myArry[0] = 1; 

myArry[1] = 23; 

myArry[2] = 3; 

myArry[3] = 4; 

myArry[4] = 5; 

myArry[5] = 22; 

myArry[6] = 7; 

myArry[7] = 8; 

myArry[8] = 9; 

myArry[9] = 22; 

myArry[10] = 12; 



if (do_dup_mal(myArry, 11, &prePos, &backPos) ) 

printf("\nT