给定一个长度为N的数组,其中每个元素的取值范围都是1到N。判断数组中是否有重复的数字。(原数组不必保留)
方法1.
对数组进行排序(快速,堆),然后比较相邻的元素是否相同。
时间复杂度为O(nlogn),空间复杂度为O(1)。
方法2.
使用bitmap方法。
定义长度为N/8的char数组,每个bit表示对应数字是否出现过。遍历数组,使用 bitmap对数字是否出现进行统计。
时间复杂度为O(n),空间复杂度为O(n)。
方法3.
遍历数组,假设第 i 个位置的数字为 j ,则通过交换将 j 换到下标为 j 的位置上。直到所有数字都出现在自己对应的下标处,或发生了冲突。
时间复杂度为O(n),空间复杂度为O(1)。
方法3示例代码如下:
bool isUnique(int *X, int N)
{
for(int i=0; i<N; ++i)
{
if(X[i] != i+1)//X[i]不在自己的位置
{
if(X[X[i]-1] == X[i])return false;
swap(X[i], X[X[i]-1]);
}
}
return true;
}
方法4.
××××××××××××××××××××××××××××××××××
固定偏移标志法
利用标记法单纯写个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符号位闲着也是闲着)
int 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;//没有重复
}
收集整理自以下两篇博客:
http://hi.baidu.com/nicker2010/item/9cbed11c6811b8707a5f2513
http://www.cnblogs.com/relang99/archive/2008/12/23/1360773.html