一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第1天,点击查看活动详情。
题目:给定一个长度为n的数组nums,在nums中所有数字都在0~n-1范围内。但数组中存在某些数字是重复的,重复的数字未知,且可能存在多个数字重复,重复的数量不定。请找出数组中重复的任何一个数字。
例如:
输入:
[2, 3, 1, 0, 2, 5, 3]
输出:2 或 3
解题思路
本题是一道简单题,也就意味着我们可以重拳出击了,哈哈哈~
HashSet 解决
看完题目,首先想到的就是HashSet,因为题目只要求找到一个重复的即可,并不需要找出所有满足条件的值,而Set和Map都具有判定当前元素是否在集合中的特性,因此可以直接使用Set来存储元素的值,之后碰到存在的直接返回即可,代码如下:
public int findRepeatNumber(int[] nums) {
HashSet<Integer> set = new HashSet<>();
for(Integer num:nums){
if(set.contains(num)){
return num;
}else {
set.add(num);
}
}
return -1;
}
时间复杂度为,空间复杂度也是。
如果要求不能使用Java自带的Set,我们可以使用数组来存储对应元素的数量,如果数量大于一定值,也直接返回,可得代码:
public int findRepeatNumber(int[] nums) {
int[] count = new int[nums.length];
for(Integer num:nums){
count[num]++;
if(count[num]>1) return num;
}
return -1;
}
时间复杂度为,空间复杂度也是。上述代码实际耗时4ms,速度较慢,将代码改成下面的:
public int findRepeatNumber(int[] nums) {
int len = nums.length;
int[] count = new int[len];
for(int i=0;i<len;i++){
count[nums[i]]++;
if(count[nums[i]]>1) return nums[i];
}
return -1;
}
复杂度没有变化,但实际耗时2ms。之所以变快是因为之前的代码使用的是foreach的遍历方式,每次存入count数组还要进行Integer到int的转化,因此建议使用下面的循环进行遍历。
排序
如果数组中存在重复元素,那排序后必然是挨着的,因此我们可以先对数组进行排序,之后判断相邻的元素是否相等即可,代码如下:
public int findRepeatNumber(int[] nums) {
Arrays.sort(nums);
int len = nums.length;
for(int i=1;i<len;i++){
if(nums[i]==nums[i-1]) return nums[i];
}
return -1;
}
但时间复杂度变成了,空间复杂度为。
原地哈希
哈希的思想是将数组中对应的元素对哈希表的长度进行取余,之后将元素填入余数的位置。原地哈希就是将数组元素保持不变,将原数组看作是一个哈希表,将数组元素存入对应下标的位置,因此本题原地哈希会有两种可能:
- 数组元素
nums[i]所对应的元素不等于i,并且nums[i]和nums[nums[i]]不相等,则此时需要将nums[i]和nums[nums[i]]进行交换。 - 数组元素
nums[i]所对应的元素不等于i,并且nums[i]和nums[nums[i]]相等,则此时直接返回即可。
代码如下:
public int findRepeatNumber(int[] nums) {
int len = nums.length;
for(int i=0;i<len;i++){
while(nums[i]!=i){
if(nums[i] == nums[nums[i]]) return nums[i];
swap(nums, nums[i], i);
}
}
return -1;
}
时间复杂度为,空间复杂度为。