【数组中重复的数】究极完整版“一题多解”
在一个长度为n的数组里的所有数字都在0——n-1的范围内。数组中的某个数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。例如输入长度为8的数组{2,3,5,4,3,1,6,7},那么输出重复的数字是3
- 解决这个问题简单的一个方法是先把输入的数组排序。从排序的数组中找出重复的数字只需要从头到尾扫描排序后的数字的代码就可以了。排序一个长度为n的数组需要O(nlogn)的时间
//数组排序
#include<bits/stdc++.h>
using namespace std;
int duplicate(int numbers[],int lenth)
{
if(numbers==NULL ||lenth<=0)
{
return -1;
}
sort(numbers,numbers+lenth);//先排序
int temp=numbers[0];//temp用来存放当前的数字
for(int i=1;i<lenth;i++)
{
if(temp==numbers[i])
return numbers[i];//如果下一个数字还等于tamp则找到重复
else
temp=numbers[i];
}
return -1;
}
int main()
{
int numbers[10]={2,3,1,6,4,5,4,7}; //4
int numbers1[10]={2,3,5,4,3,1,6,7};//3
cout<<duplicate(numbers,8)<<endl;
cout<<duplicate(numbers1,8)<<endl;
return 0;
}
- 还可以利用哈希表来解决这个问题.从头到尾按顺序扫描数组的每个数字,每扫描到一个数字的时候,判断哈希表里是否包含,如果包含就重复的数字就是它,不包含就把这个数字加进哈希表。
- 这个算法的时间复杂度是O(n),但他提高的时间效率是以一个大小为O(n)的哈希表为代价的
//哈希表
#include<bits/stdc++.h>
using namespace std;
int duplicate(int numbers[],int lenth)
{
if(numbers==NULL ||lenth<=0)
{
return -1;
}
int temp[lenth]={0}; //哈希表 0代表没有 1代表有一个 2代表有两个即重复了一个
for(int i=0;i<lenth;i++)
{
temp[numbers[i]]++;//把当前数字放进哈希表
if(temp[numbers[i]]>1)
return numbers[i];//如果哈希表已经存在这个数字则找到这个重复的数字
}
return -1;
}
int main()
{
int numbers[10]={2,3,1,6,4,5,4,7};
int numbers1[10]={2,3,5,4,3,2,6,7};
cout<<duplicate(numbers,8)<<endl;
cout<<duplicate(numbers1,8)<<endl;
return 0;
}
-
接下来介绍一种空间复杂度为O(1)的算法,重排数组。从头到尾扫描这个数组中的每个数字。当扫描到下标为i的数字,首先比较(用m表示)这个数字是不是等于i。如果是接着扫描下一个数字;如果不是,则再拿它和第m个数字进行比较。如果它和m相等则出现了重复的数字(该数字在下标为i和m的位置都出现了);如果不相等就把i和m交换位置,把m放到属于他的位置
以数组a{2,3,1,6,4,5,4,7}为例分析:- a[1]=2. a[3]=1 交换==》{1,3,2,6,4,5,4,7}
- a[1]=1 不动
- a[2]=3 a[3]=2 交换==》{1,2,3,6,4,5,4,7}
- a[2]=2 a[3]=3 不动
- a[4]=6 a[6]=5 交换==》{1,2,3,5,4,6,4,7}
- a[4]=5 a[5]=4 交换==》{1,2,3,4,5,6,4,7}
- a[4]=4 a[5]=5 a[6]=6 不动
- a[7]=4 a[4]=4 找到重复4 结束
-
是不是一目了然了呢,交换的目的是为了把数字放在本该属于他的位置,下面上代码:
//重组数组
#include<bits/stdc++.h>
using namespace std;
int duplicate(int numbers[],int lenth)
{
if(numbers==NULL ||lenth<=0)
{
return -1;
}
for(int i=0;i<lenth;i++)
if(numbers[i]<0||numbers[i]>lenth-1)
{
return -1;
}
for(int i=0;i<lenth;i++)
{
while(numbers[i]!=i)
{
if(numbers[i]==numbers[numbers[i]])
{
return numbers[i];
}
int temp=numbers[i];
numbers[i]=numbers[temp];
numbers[temp]=temp;
}
}
return -1;
}
int main()
{
int numbers0[10]={2,3,1,6,4,5,4,7};
int numbers1[10]={2,3,5,4,3,2,6,7};
int numbers2[10]={2,3,1,0,2,5,3};
cout<<duplicate(numbers0,8)<<endl;
cout<<duplicate(numbers1,8)<<endl;
cout<<duplicate(numbers2,7)<<endl;
return 0;
}
- 如果我们不修改数组找出重复的数字需要怎么做的呢?这就需要用到二分法
- 以长度为8的数组a{2,3,5,4,3,2,6,7}为例
- 1根据长度为8的所有数字都在1-7的范围内
- mid=((7-1)>> 1 )+ 1=4
- 分成1-4和5-7,统计区间中数字出现次数,很明显1-4出现了5次说明有重复的数字
- 2再把1-4分成1-2和3-4
- *很明显3-4出现了3次,说明有重复的数字
- 3再把3-4分成3-3和4-4
- 很明显3出现了两次。是一个重复的数字
你们应该注意到了,2也重复了两次,这是因为二分法不能保重找出所有重复的数字,这是因为在1-2的范围里有1和2两个数字,这个范围的数字也出现了两次,所以不能确定是每个数字各出现一次还是某个数字出现了两次
但是值得一提的是,二分法的时间复杂度是O(nlogn)但空间复杂度为O(1),相当于以时间换空间
//二分法
#include<bits/stdc++.h>
using namespace std;
int countRange(int numbers[],int lenth,int start,int end)//计算某区间内数字在某数组出现次数
{
if(numbers==NULL)return 0;
int count=0;
for(int i=0;i<lenth;i++)
{
if(numbers[i]>=start&&numbers[i]<=end)
++count;
}
return count;
}
int duplicate(int numbers[],int lenth)
{
if(numbers==NULL ||lenth<=0)
{
return -1;
}
int start=1;
int end=lenth-1;//从1-lenth找重复的数字,说明最大的数字为lenth-1
while(end>=start)
{
int mid=((end-start)>>1)+start;
int count=countRange(numbers,lenth,start,mid);//计算区间中数字出现的个数
if(start==end)
{
if(count>1)
return start;
else
return -1;
}
if(count>(mid+1-start))
end=mid;
else
start=mid+1;
}
return -1;
}
int main()
{
int numbers[10]={2,3,1,6,4,5,4,8};
int numbers1[10]={2,3,5,4,3,2,6,7};
cout<<duplicate(numbers,8)<<endl;
cout<<duplicate(numbers1,8)<<endl;
return 0;
}