数组a[N],1至N-1这N-1个数存放在a[N]中,其中某个数重复一次。写一个函数,找出被重复的数字。时间复杂度必须为o(N)函数原型:算法
int do_dup(int a[],int N)数组
××××××××××××××××××××××××××××××××××ide
假金条的数学思想函数
此算法题借鉴了假金条的思想,不过比那个过程简单,存放1至N-1,就至关于依次从各个袋中取出1至N-1个金条,可是有一个是重的,计算这N个数的和将至关于称总重量,减去1至N-1的和(用数学方法求)就可求出来重复的数。总之要充分利用数学知识性能
const int N = 5;优化
int a[N]={2,1,3,1,4};spa
int tmp1 = 0;rest
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)不仅一个重复利用此法也是能够实现的