题目:
不修改数组找出重复的数字。在一个长度为n+1的数组中的所有数字都在1~n的范围内,所以数组中至少有一个数字是重复的。在不修改输入数组的情况下找出数组中任意一个重复数字。例如输入长度为8的数组{2, 3, 5, 4, 3, 2, 6, 7},则对应输出的是2或者3。
思路:(类似于2分查找)
对于一个整数范围i1~i2,如果在这个范围内的整数的数量超过i2-i1+1,那么此范围内的整数中必然有整数会出现多次。因此选定范围1~n/2,并记录输入数组中在此范围内的元素的数量,若数量大于n/2,则在此整数范围内必然有整数在数组中出现了多次,否则在范围(n/2)+1~n内必然有整数在数组中出现多次。对确定出现重复整数的范围再进行前面的划分和计数操作,直到找到重复的数字。
代码(含测试用例)
public static void main(String[] args){
Q4 q4 = new Q4();
q4.test1();
q4.test2();
q4.test3();
q4.test4();
}
public int duplicate(int[] numbers, int length){
// robust
if(numbers == null || length <= 0)
return -1;
for(int i = 0; i < length; i++)
if(numbers[i] < 1 || numbers[i] > length-1)
return -1;
int lo = 1;
int hi = length-1;
while(lo <= hi){
int mid = (lo + hi)/2;
//int mid = ((hi - lo) >> 1) + lo;
int count = 0;
//对范围内的元素个数进行计数
for(int i = 0; i < length; i++)
{
if(numbers[i] <= mid && numbers[i] >= lo)
count++;
}
if(lo == hi)
{
if(count > 1)
return lo;
else
break;
}
// 根据检测情况调整会出现重复的整数范围
if(count > mid - lo + 1)
hi = mid;
else
lo = mid + 1;
}
return -1;
}
// no duplicate
public void test1(){
int[] nums = {1, 2, 3, 4, 5, 6};
System.out.println(duplicate(nums, nums.length));
}
// has one duplicate
public void test2(){
int[] nums = {1, 2, 4, 5, 6, 3, 1};
System.out.println(duplicate(nums, nums.length));
}
// multiple duplicates
public void test3(){
int[] nums = {1, 1, 3,4,5,3,2, 7};
System.out.println(duplicate(nums, nums.length));
}
// null array
public void test4(){
System.out.println(duplicate(null, 0));
}
性能:代码中含有两重循环,外层循环为二分查找,故时间复杂度为O(lgn),内循环时间复杂度为O(n),故总的时间复杂度为O(nlgn)。空间复杂度为O(1)。