首页 > 代码库 > 数组a[N],存放了1~N-1个数,其中某个数重复一次【转】

数组a[N],存放了1~N-1个数,其中某个数重复一次【转】

本文转自:http://blog.csdn.net/zhuimengzh/article/details/6720388

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

 

方法一:

[cpp] view plain copy
 
  1. //好算法,因为第二个重复的数字其位置肯定不会变化,而a[a[0]]正好可以访问到每一个位置的数据。  
  2. int do_dup(int a[],int N){  
  3.     int temp;  
  4.     //a[0]为监视哨  
  5.     while (a[0]!=a[a[0]])  
  6.     {  
  7.         temp=a[0];  
  8.         a[0]=a[temp];  
  9.         a[temp]=temp;  
  10.     }  
  11.     return a[0];  
  12. }  
  13.   
  14. void main(){  
  15.     int a[5]={1,2,3,2,0};  
  16.     cout<<do_dup(a,5);  
  17.     system("pause");  
  18. }  

 

方法二:

[cpp] view plain copy
 
  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. int do_dup(int a[],int N){  
  5.     int *p=new int[N];  
  6.     int i;  
  7.     for (i=0;i<N;i++)  
  8.         p[i]=-1;  
  9.     for (i=0;i<N;i++)  
  10.     {  
  11.         if (p[a[i]]==-1)  
  12.             p[a[i]]=a[i];  
  13.         else{  
  14.             cout<<a[i]<<endl;  
  15.         }  
  16.     }  
  17.     delete [] p;  
  18.     return 0;  
  19. }  
  20.   
  21.   
  22. void main(){  
  23.     int a[5]={1,2,3,2,0};  
  24.     do_dup(a,5);  
  25.     system("pause");  
  26. }  


 

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

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

解决办法:

 

数组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("\n

数组a[N],存放了1~N-1个数,其中某个数重复一次【转】