找出数组中重复的数字
问题描述:
力扣链接
在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
方法一:排序做法
将数组进行排序,排序后再查找重复数字。
class Solution {
public int findRepeatNumber(int[] nums) {
Arrays.sort(nums);
int pre = nums[0];
for(int index=1; index < nums.length; index++){
if(pre == nums[index])
return pre;
pre = nums[index];
}
return -1;
}
}
时间复杂度:O(nlogn),空间复杂度:O(1)
方法二:哈希表
从头到尾按顺序扫描数组的每个数字,每扫描到一个数字的时候,都可以用O(1)的时间来判断哈希表里是否存在这个数字,如果不存在的话,就把该数字加入到这个哈希表中,如果存在的话就说明该数字重复。
class Solution {
public int findRepeatNumber(int[] nums) {
int n = nums.length;
Set<Integer> set = new HashSet<Integer>();
for(int index=0;index<n;index++){
if(set.contains(nums[index]))
return nums[index];
set.add(nums[index]);
}
return -1;
}
}
时间复杂度:O(n),空间复杂度:O(n)
方法三:原地置换法
即书中所提到的方法:当扫描到下标为i的数字时,首先比较这个数字(用m表示)是否等于i,如果相等,扫面下一个数字;如果不相等,将它与下标为m的数组中数字进行比较,如果相等就说明该数字时重复的(再下标和im的位置都出现了),如果不相等,就把下标为i(值为m)与下标为m的数组中的两个数进行互换,把m放到属于他的位置。接下来重复这个比较、交换的过程,直到我们发现一个重复的数字。
class Solution {
public int findRepeatNumber(int[] nums) {
int n = nums.length;
for(int index=0;index<n;index++){
while(nums[index] != index){
if(nums[index] == nums[nums[index]])
return nums[index];
int temp = nums[index];
nums[index] = nums[temp];
nums[temp] = temp;
}
}
return -1;
}
}
小心错误交换方式:num[index]的值再第二行时就已经改变了
int temp = nums[index];
nums[index] = nums[nums[index]];
nums[nums[index]] = temp;
时间复杂度:O(n),空间复杂度:O(1)。时间复杂度解释:代码中经管有一个二重循环,但每个数字最多两次交换就能找到属于它的位置。
总结:第一种方式最简单明了,但是时间复杂度最高,空间复杂度较低;第二种方法时间复杂度较低,但是空间复杂度最高;第三种方式时间复杂度和空间复杂度都是最低的,也就是书中的方法。
这题是不能用二分解的,原因就出在数组的长度上面,因为278题长度n+1,1 ~ n代表必定有一个数字出现两次,所以你用二分有个区间的数字在原数组中出现次数是>这个区间范围的,但是对这题而言,长度n,0 ~ n-1且有重复,代表有数重复,就有数不存在,这样长度才符合,这将会导致可能重复的数和不存在的数在同一个区间你无法找出来。
不修改数组找到重复数字
问题描述:
力扣链接
Given an array nums containing n + 1 integers where each integer is between 1 and n (inclusive), prove that at least one duplicate number must exist. Assume that there is only one duplicate number, find the duplicate one.
原地置换法
由于不能修改数组,可以使用一个辅助数组来存储上述的数组,然后再用原地置换法解决问题,这种方法的时间复杂度为O(n),空间复杂度为O(n)。参考上面的代码即可。
二分查找法
将1 ~ n的数字从中间的数字m分为两个部分,前面一半是1 ~ m,后面的一半为m+1 ~ n,如果1 ~ m中的数字的个数超过了m,那么这一区间一定重复的数字,否则,另一个区间里一定会有重复的数字。将有重复的数字的区间再一分为二,直到找到哪个重复的数字。
class Solution {
public int findDuplicate(int[] nums) {
int n = nums.length;
int start = 1;
int end = n -1;
while (end >= start) {
int middle = ((end - start) >> 1) + start;
int count = countRange(nums,start,middle);
if (end == start) {
if (count > 1)
return start;
else
break;
}
if (count > (middle - start + 1)) {
end = middle;
} else {
start = middle + 1;
}
}
return -1;
}
int countRange(int[] nums, int start, int end) {
int count = 0;
for (int i = 0; i < nums.length; i++) {
if (nums[i] >= start && nums[i] <= end)
count++;
}
return count;
}
}
输入长度为n的数组,那么函数countRange将被调用O(logn)次,每次需要O(n)的时间。所以该方法的时间复杂度为:O(nlogn),空间复杂度为O(1)。时间换空间。