题目要求:
在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。
解析:
方法一:
先将数组排序,从排序的数组中找出重复的数字是一件很容易的事情,但是这样时间复杂度O(nlogn)很高
方法二:
因为长度为n的数组里所有数字都在0~n-1的范围内,如果没有重复的数字的话,那么数组排序后数字i将出现在下标为i 的位置。但是由于有重复的数字,所以有些位置可能存在多个数字,有些位置可能没有数字。
我们先拿一个具体的例子,来描述一下我们的这个方法
例如数组{2,3,1,0,2,5,3}。第一个数为2 != 下标0,所以把2交换到下标为2的位置去。
i++,继续比较,第二个数3 != 下标1,所以把3交换到下标为3的位置去
i++,继续比较,第三个数2 == 下标2,不交换。
i++,继续比较,第四个数3 == 下标3,不交换。、
i++,继续比较,第五个数2 != 下标4,比较number[4] == number[numver[4]].。因此找到了一个重复的值
代码实现:
bool duplicate(int number[], int length, int* duplicate)
{
if (number == nullptr || length <= 0)
{
return false;
}
for (int i = 0; i < length; i++)
{
if (number[i]<0 || number[i]>length - 1)
{
return false;
}
}
for (int i = 0; i < length; i++)
{
while (i != number[i])
{
if (number[i] == number[number[i]])
{
*duplicate = number[i];
return true;
}
int tmp = number[i];
number[i] = number[tmp];
number[tmp] = tmp;
}
}
return false;
}
题目改进:
在上述题目的基础上要求不能输入的数组
解析:
方法一:
我们创建一个长度为n+1的辅助数组,然后逐一把原数组的每一个数字复制到辅助数组,把值为m的数字复制到小标为m的位置。但是这样做的空间复杂度为O(n)。所以我们想出了另外一个方法
方法二:
还是以具体的数组来加以说明,例如数组{2,3,5,4,3,2,6,7}。有8个数字。中间的4把数组分成两部分,一段是1-4,另一段是5-7.
1-4里面的个数大于后者,所以重复数字一定在1-4之间。
再把中间的2,分成两组,1-2,3-4
3-4里面的个数大于前者,所以重复数字一定在3-4之间。
再分别统计这两个数字在数组中出现的次数。
代码实现如下:
int getcount(int* number, int length, int start, int end)
{
if (number == nullptr)
return 0;
int count = 0;
for (int i = 0; i < length; i++)
{
if (number[i] >= start && number[i] <= end)
{
count++;
}
}
return count;
}
int getduplicate(int* number, int length)
{
if (number == nullptr || length <= 0)
return -1;
int end = length - 1;//7
int start = 1;//1
while (end >= start)
{
int middle = ((end - start) >> 1) + start;//4
int count = getcount(number, length, start, middle);
if (end == start)
{
if (count > 1)
return start;
break;
}
if (count > (middle - start + 1))//5>4 所以在1-4里面再划分
end = middle;
else
start = middle;
}
return -1;
}
这种方法类似于二分查找,函数getcount被调用O(logn)次,每次需要O(n)的时间,所以时间复杂度为O(nlogn).但是空间复杂度是O(1)。这是一个时间换空间的算法。