一、题目
在一个长度为n的数组里的所有数字都在0到n-1的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。例如,如果输入长度为7的数组{2, 3, 1, 0, 2, 5, 3},那么对应的输出是重复的数字2或者3。
二、解法
2.1 方法一
先把输入的数组排序,从排序的数组中找出重复的数字是一件很容易的事情,只需要从头到尾扫描排序数组后的数组就可以了,排序一个长度为n的数组需要O(nlogn)的时间。下面代码为:先对数组快速排序,然后从头到尾扫描数组,数组中前一个数若和后一个数相同,则将该数组赋值给duplication, 并返回true。
class Solution {
public:
// Parameters:
// numbers: an array of integers
// length: the length of array numbers
// duplication: (Output) the duplicated number in the array number
// Return value: true if the input is valid, and there are some duplications in the array number
// otherwise false
int Partition(int a[], int low, int high)
{
int x = a[high];//将输入数组的最后一个数作为主元,用它来对数组进行划分
int i = low - 1;//i是最后一个小于主元的数的下标
for (int j = low; j < high; j++)//遍历下标由low到high-1的数
{
if (a[j] < x)//如果数小于主元的话就将i向前挪动一个位置,并且交换j和i所分别指向的数
{
int temp;
i++;
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
//经历上面的循环之后下标为从low到i(包括i)的数就均为小于x的数了,现在将主元和i+1位置上面的数进行交换
a[high] = a[i + 1];
a[i + 1] = x;
return i + 1;
}
void QuickSort(int a[], int low, int high)
{
if (low < high)
{
int q = Partition(a, low, high);
QuickSort(a, low, q - 1);
QuickSort(a, q + 1, high);
}
}
bool duplicate(int numbers[], int length, int* duplication) {
if (numbers==nullptr||length<0)
return false;
for(int i=0; i<length; ++i)
{
if(numbers[i]<0||numbers[i]>length-1)
return false;
}
QuickSort(numbers, 0, length-1);
for(int i=0; i<length-1; ++i)
{
if (numbers[i] == numbers[i+1])
{
*duplication = numbers[i];
return true;
}
}
return false;
}
};
2.2 方法二: hash table
利用哈希表来解决这个问题,从头到尾按照顺序扫描数组的每个数字,没扫描到一个数字的时候,都可以用O(1)的时间来判断哈希表里是否包含了该数字,如果哈希表还没有这个数字,就把它加入哈希表,如果哈希表里已经存在了该数字,就找到了该数字,这个算法的时间复杂度为O(n),但是它提高时间效率是以一个大小为O(n)的哈希表为代价的。
class Solution {
public:
// Parameters:
// numbers: an array of integers
// length: the length of array numbers
// duplication: (Output) the duplicated number in the array number
// Return value: true if the input is valid, and there are some duplications in the array number
// otherwise false
bool duplicate(int numbers[], int length, int* duplication) {
if (numbers==nullptr||length<0)
return false;
for(int i=0; i<length; ++i)
{
if(numbers[i]<0||numbers[i]>length-1)
return false;
}
map<int, int> map1;
for(int i=0; i<length; ++i)
{
if (map1.find(numbers[i])!=map1.end())
{
map1[numbers[i]]++;
}
else
map1[numbers[i]]=0;
}
for(int i=0; i<length; i++)
{
if(map1[numbers[i]]>0){
duplication[0] = numbers[i];
return true;
}
}
return false;
}
};
2.3 方法三
数组中的数字都在0~n-1的范围内,如果数组中没有重复的数字,那么数组排序之后数字i将出现在下表为i的位置,由于数组中有重复的数字,有的位置可能存在多个数字,同时有的位置可能没有数字。
我们重排这个数组,从头到尾依次扫描这个数组中的每个数字,当扫描到下表为i的数字(用m表示)时,首先比较这个数字是不是等于i。如果是,则接着扫描下一个数字;如果不是,则再拿它和第m个数字进行比较,如果它和第m个数字相等,就找到了一个重复的数字(该数字在下表i和m的位置都出现了);如果它和第m个数字不相等,就把第i个数字和第m个数字交换,把m放到属于它的位置,接下来再重复这个比较、交换过程,知道我们发现了一个重复的数字。
class Solution {
public:
// Parameters:
// numbers: an array of integers
// length: the length of array numbers
// duplication: (Output) the duplicated number in the array number
// Return value: true if the input is valid, and there are some duplications in the array number
// otherwise false
bool duplicate(int numbers[], int length, int* duplication) {
if (numbers==nullptr||length<0)
return false;
for(int i=0; i<length; ++i)
{
if(numbers[i]<0||numbers[i]>length-1)
return false;
}
for(int i=0; i<length; ++i)
{
while(i != numbers[i])
{
if (numbers[i] == numbers[numbers[i]])
{
*duplication = numbers[i];
return true;
}
int temp = numbers[i];
numbers[i] = numbers[temp];
numbers[temp] = temp;
}
}
return false;
}
};
代码中尽管有一个两重循环,但每个数字最多只要交换两次就能找到属于它自己的位置,因此总的时间复杂度为O(n)。另外,所有的操作步骤都是在输入数组上进行的, 不需要额外分配内存,因此时间复杂度为O(1)。