题目二:不修改数组找出重复的数字
在一个长度为n+1的数组中的所有数字都在1~n的范围内,所以数组中至少有一个数字是重复的。在不修改输入数组的情况下找出数组中任意一个重复数字。例如输入长度为8的数组{2, 3, 5, 4, 3, 2, 6, 7},则对应输出的是2或者3。
题解1:
思路:创建一个长度为n+1的辅助数组,然后逐一把原数组的每个数字复制到辅助数组。如果原数组中被复制的数字是m,则把它复制到辅助数组中下标为m的位置,当发现该位置已有一个与下标相同的值时,就找到一个重复数字了。需要O(n)的辅助空间,时间复杂度为O(n)。
代码实现:
class Solution
{
public static void findTheRepeatNum(int []arr)
{
int newArr[] = new int[arr.length+1];
for (int i=0;i<arr.length;i++)
{
if (arr[i]!=newArr[arr[i]])
newArr[arr[i]]=arr[i];
else
{
System.out.println(arr[i]);
return;
}
}
}
}
解法2:
思路:按照二分查找的思路,将数组中1~n的数字从中间数m分为两部分(是数字大小的中间,而非位置上的中间),前面一半为1~m,后面一半为m+1~n。如果数组中1~m的数字数目超过m,那么重复数字一定出现在这段区间内,反之,重复数字一定出现在m+1~n这段区间内。我们可以重复对包含重复数字的区间进行划分,直到找到这个重复数字。
以题目中的长度为8的数组{2,3,5,4,3,2,6,7}为例:
根据题意,该数组中所有数字都在1~7之间,所以用来划分区间的界限数m为4,将其分为1~4和5~7两个部分。
然后统计1~4四个数字在数组中出现的次数,一共出现5次,所以1~4四个数字中,一定有一个以上的重复数字。
接着再把1~4划分为{1,2},{3,4}两个部分,分别统计数字{1,2}和{3,4}出现的次数,前者两次,后者三次,所以,可以判断,3,4中一定有一个数重复了。
最后分别统计3和4在数组中出现次数,发现3出现了两次,所以找到了一个重复的数字3。
该解法的时间复杂度为O(nlogn),但空间复杂度仅为O(1),较之解法一,相当于是以时间换空间。
这种解法无法找出所有重复数字,因为[1,2]这个区间内有1,2两个数字,他们共出现了两次,但是1没有出现,2出现了两次,所以这里被默认为没有重复数字跳过了。
代码实现:
class Solution {
public static void findTheRepeatNum(int []arr) {
if (arr==null||arr.length<1)
return;
int length = arr.length;
int begin = 1;
int end = length-1;
while (end>=begin)
{
int mid = ((end-begin)>>1)+begin; //求出分界值
int count = countRange(arr,length,begin,mid); //统计次数
if (end==begin)
{
//当mid begin end三者重合时(区间内只有一个数字),判断出现次数是否大于1,
if (count>1) {
System.out.println(begin);
return;
}
else
break;
}
//判断重复数字在哪一边
if (count>(mid-begin+1)) //如果左边的数字出现的次数大于界限所有的数的数量
end=mid; //则将分界值作为右边界
else
begin=mid+1; //否则将分界值后一个数作为左边界
}
return;
}
//统计出现的次数
public static int countRange(int[]arr,int length,int begin,int end)
{
if (arr==null) //数组为空
return 0;
int count=0;
for (int i=0;i<length;i++)
if (arr[i]>=begin&&arr[i]<=end) //统计区间内的数在数组中出现的次数
count++;
return count;
}
}