题目一:在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。
解法一:先对数组排序,然后比较相邻的数组元素是否相等
public boolean duplicate(int numbers[],int length,int [] duplication) {
if (numbers==null || length==0) {
return false;
}
Arrays.sort(numbers);
for(int i=0;i<length-1;i++) {
if(numbers[i]==numbers[i+1]) {
duplication[0]=numbers[i];
return true;
}
}
return false;
}
解法二:数组中的数字都在0~n-1的范围内,如果这个数组中的没有重复的数字,那么当数组排序后数字i将出现在下标为i的位置。由于出现重复数字,有些位置存在多个数字,有些位置可能没有数字。
从头到尾扫描数组中的每个数字,当扫描到下标为i的数字,首先比较这个数字(用m表示)是否等于下标i,如果是,则扫描下一个坐标。如果不是,则拿这个m与下标为m的数字(n)进行比较。如果与这个数字相等,则找到一个重复数字(该数字在下标为i和m的位置都出现了)。如果与下标为m的数字不相等,则把下标为i的数字(m)和下标为m的数字(n)进行交换。把m放到属于自己的位置。重复这个比较,交换的过程,直到找到一个重复的数字。
下面来看看一个比较,交换的过程
原始数组:{2,3,1,0,2,5,3}
第一次 i=0:
//数组第0个元素是2,与下标不等,然后与下标为2的1交换
{1,3,2,0,2,5,3}
//数组第0个元素是1,与下标不等,然后与下标为1的3交换
{3,1,2,0,2,5,3}
//数组第0个元素是3,与下标不等,然后与下标为3的0交换
{0,1,2,3,2,5,3}
第二次 i=1:
//二三四次都与下标相等
{0,1,2,3,2,5,3}
第三次 i=2:
{0,1,2,3,2,5,3}
第四次 i=3:
{0,1,2,3,2,5,3}
第五次 i=4
扫描到下标为4的数字2,与比较下标为2的数字2进行比较,相等,找到一个重复数字
代码如下:
public boolean duplicate(int numbers[],int length,int [] duplication) {
if (numbers==null|| length<=0) {
return false;
}
for(int i=0;i<length;i++) {
if(numbers[i]<0||numbers[i]>length-1) {
return false;
}
}
for(int i=0;i<length;i++)
{
while(numbers[i]!=i){
if(numbers[i]==numbers[numbers[i]])
{ duplication[0]=numbers[i];
return true;
}
swap(numbers,i,numbers[i]);
}
}
return false;
}
public void swap(int[] num,int p,int q){
int tmp=num[p];
num[p]=num[q];
num[q]=tmp;
}
题目二:在一个长度为n+1的数组里的所有数字都在1~n的范围内,所以数组中至少有一个数字是重复的。请找出数组中任意一个重复的数字,但不能修改输入的数组。
方法一:创建一个n+1的数组,逐个把原数组的每个数字复制到辅助数组。如果原数组中被复制的数字是m,则把它复制到辅助数组下标为m的位置。这样就很容易发现哪个数字是重复的。由于需要创建一个数组,该方案需要o(n)的辅助空间。
public static int getDulicate1(int[] arr,int length){
if(arr==null || arr.length<=0)
{
System.out.println("输入的数组无效");
return -1;
}
for(int a:arr)
{
if(a<1 || a>arr.length-1)
{
System.out.println("数字大小超出范围");
return -1;
}
}
int[] arr1 =new int[length];
int n=0;
for(int i=0;i<length-1;i++)
{
n=arr[i];
if(arr1[n]!=0) {
return n;
}
else
{
arr1[n] = arr[i];
}
}
return 0;
}
方法二:把从1~n数字从中间的数字m分为两部分。前面一半为1~m,后面的一半为m+1~n。如果1~m的数字的数目超过m,那么这一半的区间一定包含重复的数字。反之则是,m+1~n中包含重复的数字。可以继续把包含重复数字的区间一分为2,直到找到一个重复的数字
public static int getDulicate2(int[] arr){
System.out.println("进入方法");
if(arr==null || arr.length<=0)
{
System.out.println("输入的数组无效");
return -1;
}
for(int a:arr)
{
if(a<1 || a>arr.length-1)
{
System.out.println("数字大小超出范围");
return -1;
}
}
int low=1;
int high=arr.length-1;
int mid,count;
while (low<=high)
{
mid=(low+high)/2;
count=countRange(arr,low,mid);
if(low==high)
{
if (count>1)
return low;
else
break;
}
if(count>mid-low+1)
{
high=mid;
}
else
{
low=mid+1;
}
}
return -1;
}
/**
* 判断这个范围内数字出现的次数
* @return
*/
private static int countRange(int[] arr, int low, int high) {
if(arr==null)
{
return 0;
}
int count=0;
for(int a:arr){
if(a>=low && a<=high)
{
count++;
}
}
return count;
}
如果输入的长度为n的数组,那么函数CountRange将会被调用o(logn)次,每次需要o(n)的时间,因此总的时间复杂度是o(nlogn)。空间复杂度是o(1)。
这个算法不能保证找出所有的重复数字。例如不能找到数组{2,3,5,4,3,2,6,7}中的重复数字2。这是因为在1~2的范围内有1和2两个数字,这个范围的数字也出现了2次。使用这个算法不能确定是每个数字各出现一次还是某个数字出现了两次。