题目描述
在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。
第一种:最朴素的做法
可以使用排序方法来做,例如使用快排等。然后再遍历一次数组就可以找到重复的数字了。时间复杂度是O(nlogn)。这是我目前知道的算法中,时间复杂度较好的。
第二种:利用空间换取时间
我们可以使用HashMap来保存我们迭代过程中遍历的值,在迭代过程中,如果发现HashMap里面包含了我们目前遍历到的值,则可以直接放回该值了。时间复杂度是O(n),空间复杂度也是O(n)。
使用其他数据结构也都是可以,不过第二种方法我们可以再进行优化一下。
第三种:最推荐,时间复杂度是O(n),空间复杂度是O(1)
这里有一个关键点(第三种方法需要这个前提条件),就是该数组元素的值都是在0 ~ n-1 的范围内。
为了方便理解(第三种做法),让我先借助一个布尔数组,生成一个长度为n的布尔数组。
我们可以一边遍历元素,一遍让元素的值作为下角标,先访问布尔数组对应位置的值,如果为false,我们就设置为true,然后继续数组的遍历;如果已经是true,那么该下角标的数值就是我们要的答案。
注意注意: 下面开始讲重点了(敲黑板~~~~~)
我们使用一个布尔数组就是为了标记我们遍历过这个数字。那么,我们能不能在遍历的过程中,在原数组上面下手呢?
当然是可以,因为我们有上面提到的那个关键点:数组元素的值都是0 ~ n-1的范围内。 为了方便,我们将array作为我们需要寻找重复数字的数组。
所以,在迭代过程中,我们将目前访问的元素的值作为下角标(例如某数字x),将array[x] 加上 我们数组的长度(相当于设置为true)。当然在赋值之前判断一下array[x] 是不是 大于等于 length(小于length,可以理解为false),是的话,x就是我们要的答案。在迭代过程中,元素里面的值有可能大于等于length,在作为下角标时,我们需要先进行求模运算,再查找。
本人文才不好,可能这上面一段话会让人看不懂。 所以,我们不妨这样看,用元素的值作为下角标,我们就具体地作为数字看,大于等于length的,就求模; 通过元素的值作为下角标 访问到的 值 ,当成布尔值。小于length的,当成false;大于等于length的,当成true,且下角标就是我们要的答案。
例子:
{2,3,1,0,2,5,3},长度为7
第一次迭代: index = array[0] ,array[index] 为 1。是false(值小于7),需要将其设置为true (值加上7) ,数组就变成了{2,3,8,0,2,5,3}
第二次迭代: index = array[1] ,array[index] 为 0 。还是false 。处理与第一次迭代一样。之后数组变成
{2,3,8,7,2,5,3}
第三次迭代: index= array[2] ,元素值大于length了,经过求模7后,index = 1,array[index] =3 ,相同处理,数组变成
{2,10,8,7,2,5,3}
第四次迭代后,数组变成{9,10,8,7,2,5,3}
第五次迭代: index = array[4], array[index] =8,是true(大于等于length),所以index 就是第一个重复的数字,是2。
结束迭代。
下面附上代码:
public boolean duplicate(int numbers[]) {
if(numbers == null)
return false;
int length = numbers.length;
int result = -1;
int index = 0;
for(int i = 0;i < length;i++){
index = numbers[i];
//将元素的值作为下角标,大于等于length的就需要进行求模运算
if(index >= length)
index = index % length;
//如果是true
if(numbers[index] >= length){
result = index;
break;
}else if(numbers[index] < length){
//如果为false
numbers[index]+= length;
}
}
System.out.println(result);
//等于-1就是没有重复数字
if(result < 0)
return false;
else
return true;
}
文笔不好,请见谅! 如果有错,欢迎提出。