面试题3:数组中重复的数字
题目二:不修改数组找出重复的数字
在一个长度为n+1的数组里的所有数字都在1~n的范围内,所以数组中至少有一个数字是重复的。请找出数组中任意一个重复的数字,但不能修改输入的数组。例如,如果输入长度为8的数组{2,3,5,4,3,2,6,7},那么对应的输出是重复的数字2或者3。
解决方案1:
题目要求不能修改输入的数组,可以创建一个长度为n+1的辅助数组,然后逐一把原数组的每个数字复制到辅助数组。如果原数组中被复制的数字是m,那么就把它复制到辅助数组下标为m的位置。比较简单,但是空间复杂度是O(n);
优化方案:(空间复杂度n(1) 时间复杂度O(nlogn) )
数组长度为n+1,数组里所有的数字都在1~n的范围内,数组里总共有大于n个数,所以一定包含重复数字。
把从1~n的数字从中间的数字m分为两部分,前面一半为1到m,后面一半为m+1到n。
如果数组中1到m的数目超过m,就说明这一半的区间里一定包含有重复数字;否则,另一半m+1~n的区间里一定包含重复的数字。我们可以继续把包含重复数字的区间一分为二,直到找到一个重复的数字。
以【2 3 5 4 3 2 6 7】为例,8个数字,数组中所有数字都在1~7的范围内。中间的数字是4,把1到7分为两个范围,分别是1到4、5到7,然后统计数组中的数在1到4这个区间出现的次数,一共出现了五次,大于四,所以1到4这个区间一定存在重复数字。
再把1~4一分为二,1到2、3到4。数字1或2在数组中一共出现了两次,再统计数字3或者4在数组中出现的次数,他们一共出现了三次,意味着3到4中肯定有重复的数字。再分别统计这两个数在数组中出现的次数。最后发现3出现了两次,确定是重复数字。
class Solution {
public static int f(int[] arr,int len) {
if (arr == null || len <= 0) return -1;
//从1到n
int start = 1;
int end = len - 1;
while (end >= start) {
int mid = ((end - start) >> 1) + start;
int count = countRange(arr, len, start, mid);
if (end == start) { //当起始数跟结束数相同时
if (count > 1) return start; //count大于一表示start这个数在数组中重复(不止一个)
else break;
}
if (count > (mid - start) + 1) {
end = mid; //当count大于n的中间数时,说明在1到n/2中间有重复数,将end=mid进行递归
} else {
start = mid + 1; //当count小于n的中间数时,说明在(n/2) + 1到n中间有重复数
}
}
return -1;
}
//统计有多少个数是在start 到end这个范围内的
public static int countRange(int[] arr,int len,int start,int end) {
if(arr == null) return 0;
int count = 0;
for(int i = 0; i < len; i++) {
if(arr[i] >= start && arr[i] <= end) {
++count;
}
}
return count;
}
}