题目:
在一个长度为n+1的数组里的所有数字都在1~n的范围内,所以数组中至少有一个数字是重复的。请找出数组中任意一个重复的数字,但是不能修改输入的数组。例如,如果输入长度为8的数组{2,3,5,4,3,2,6,7},那么对应的输出是重复的数字2或者3。
看到题目,总觉得有种似曾相识的感觉,但却不会做,痛定思痛,于今日写下总结:
当然,很多读者包括笔者自己,看算法博客一般都喜欢看最优方案,遇到什么方案X之类的,直接跳到最后.....
ok,请读者不用滑动飞轮,以下就是据笔者所知,最优的方案:
方案一,二分查找 + 计数。
咳咳,笔者假装解释一下该算法的思路:
做人要讲道理,所以算法也要。
且看数组 arr = [1, 2, 3, 4, 5],再看题设,长度为 n + 1的数组,得出 n = 5。
又有,数字在 1~n 之间,所以数组中的数字,只能是在区间 [1, 5]之间。
统计每个数字在整个数字出现的次数:
计数数值: 1,2,3,4,5 ---------长度范围为5
出现次数: 1,1,1,1,1 ---------出现次数总和:1 + 1+ 1 + 1 + 1 = 5
明显,5 = 5。没错,arr中没有重复数字!..........emmmm
哎,说了半天,笔者也不知道再说些什么了....但还是要硬着头皮往下写.....
在此,笔者厚颜向读者提出一个问题:为什么数组中会出现重复数字?
讲道理,假设数组中没有重复数组,范围1~n中数字的个数就是n,由于数组中包含超过n个数字,所以一定包含重复数字。
额.....有点抽象。
不慌,请读者再看数组 arr2 = {2,3,5,4,3,2,6,7},所有数字都在1~7之间,中间的4把数组分成两段(折半)。
第一段:{2,3,5,4}
第二段:{3,2,6,7}
分段后,开始计数,即统计每一段中的数字,在整个数组(没有折半前,即arr2)中出现的次数。
第一段计数开始:
计数数值: 2,3,5,4 ---------长度范围为4
出现次数: 2,2,1,1 ---------出现次数总和:2 + 2 + 1 + 1 = 6
很明显,6 > 4 , 说明第一段中绝逼有重复数字!
然后,继续折半,计数。
/**
* 折半,计数部分
*/
public static int isCount(int[] arr, int start, int end){
//参数检查
if(arr == null) return -1;
if(start > end) return -1;
int count = 0; //计数,数字出现的次数
//计数开始
for(int i = 0, len = arr.length; i < len; i++){
if(arr[i] >= start && arr[i] <= end){
count++;
}
}
return count;
}
public static int getDupFunction(int[] arr){
int start = 1; //起始
int end = arr.length - 1; //结束
while(end >= start){
//右移
int middle = ((end - start) >> 1) + start;
//计数
int count = isCount(arr, start, middle);
//首尾相等
if(end == start){
if(count > 1){
return start; //当前数
}else{
break;
}
}
//前一段
int part = middle - start + 1;
if(count > part){ //满足,说明(start, middle)区间中存在重复数字
end = middle; //计算下标, (start , middle)
}else{ //后一段存在重复数字
start = middle + 1;
}
}
return -1;
}
public static void main(String[] args) {
int n;
@SuppressWarnings("resource")
Scanner in = new Scanner(System.in);
System.out.println("输入数组长度:");
n = in.nextInt();
if(n < 0) return ;
int[] arr = new int[n];
for(int i = 0; i < n; i++){
System.out.println("请输入数组第" + (i + 1) + "个数:");
arr[i] = in.nextInt();
}
int sub = getDupFunction(arr);
System.out.println("重复数数字:" + sub);
}
写完代码后,你会发现,该代码只能找出重复数字3,但是2找不出来。
所以,该算法,不能保证找出所有重复数字。
好了,看完方案一,我们再看方案二:
方案一,空间换时间,即创建一个n + 1长度的数组,将原数组每一项的值放在,与之下标对应的位置。
Such As,数值为2的应该放在arr[2],数值为3的应该应该放在arr[3]中,以此类推......
写到这里,笔者也不想写了,该方案很多博客上都有。
所以........
/**
* 空间换时间
*/
public static void main(String[] args) {
int[] arr = {2, 3, 5, 4, 3, 2, 6, 7};
int[] tempArr = new int[arr.length + 1];
for(int i = 1; i <= arr.length; i++){
if(tempArr[arr[i]] != arr[i]){
tempArr[arr[i]] = arr[i];
}else{
System.out.println("重复数字:" + arr[i]);
}
}
}