剑指Offer_#3_数组中重复的数字
Contents
题目
找出数组中重复的数字。
在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
示例 1:
输入:
[2, 3, 1, 0, 2, 5, 3]
输出:2 或 3
限制:
2 <= n <= 100000
思路分析
方法1:使用HashSet或HashMap
HashSet和HashMap都是java当中collection框架里的一部分,他们的区别如下:
我们可以把数组元素加入HashSet或者HashMap(作为键),利用了这两种数据结构数据的唯一性,当遇到重复的数组元素的时候,就直接返回这个元素即可。
方法2:原地交换数组元素
因为题目当中限制了数组元素的大小是在0~n-1范围内,如果每个数字只出现一次,刚好可以按顺序排列在长度为n的数组当中。
那么可以将每个数组元素的数字(nums[i]
)放到下标nums[i]
处,这样处理之后,所有位置满足nums[i] == i
。
算法流程
循环遍历整个数组,
- 如果
nums[i] == i
,满足要求,所以不做处理 - 如果
nums[i] != i
,进行判断- 如果
nums[i] == nums[nums[i]]
,说明出现重复了,返回nums[i]
- 如果
nums[i] != nums[nums[i]]
,说明还没有放到应该在的位置,将nums[i]
与nums[nums[i]]
交换位置。
- 如果
比起方法1,这个方法空间没有借助额外的变量,空间复杂度是O(1)
方法3:排序后比较相邻元素
先将数组排序,那么重复数字必然相邻。然后遍历排序后的数组,每次比较当前数字和后一个数字,如果相同,返回当前数字。
解答
解答1:使用HashSet
class Solution {
public int findRepeatNumber(int[] nums) {
Set<Integer> set = new HashSet<Integer>();
for(int i = 0; i <= nums.length - 1;i++){
if(!set.contains(nums[i])) set.add(nums[i]);
else return nums[i];
}
return -1;//表示遍历了nums,也没有找到重复的
}
}
复杂度分析
时间复杂度:O(n)
空间复杂度:O(n)
关于HashSet
疑问:
这里边直接就使用Set,这个Set属于什么类型呢?是一个接口?还是抽象类?还是具体类?
解答:
Set是接口,不可以实例化,但是Set类型的变量可以指向实现了Set接口的实体类的对象。
例如:
Set<int> set = new HashSet<int>();
(X)泛型的尖括号中间只可以传入包装类或者自定义的类,不可以传入类似于int
这样的基本类型。
正确写法:
Set<Integer> set = new HashSet<Integer>();
疑问:
能否这么写?set.addAll(nums)
解答:
不可以,nums是普通数组,而非合集框架里边的类,所以不可以作为addAll
这个方法的参数。
解答2:原地交换数组元素
class Solution {
public int findRepeatNumber(int[] nums) {
for(int i = 0;i <= nums.length-1;i++){
if(nums[i] != i){//这里也可以写while,但是我觉得用while很奇怪,反正写if或者while都能过
if(nums[i] == nums[nums[i]]) return nums[i];
else{
int tmp = nums[i];
nums[i] = nums[tmp];
nums[tmp] = tmp;
}
}
}
return -1;//没找到
}
}
复杂度分析
时间复杂度:O(n)
空间复杂度:O(1)
解答3:排序后比较相邻元素
class Solution {
public int findRepeatNumber(int[] nums) {
Set<Integer> set= new HashSet<>();
for(int num:nums){
if(set.contains(num)) return num;
set.add(num);
}
return -1;
}
}
复杂度分析
时间复杂度:O(nlogn),因为排序的复杂度是O(nlogn)
空间复杂度:O(1)