4. 不修改数组找出重复的数字
问题描述:
在一个长度为n+1的数组里的所有数字都在1~n的范围内,所以数组中至少有一个数字是重复的。请找出数组中任意一个重复的数字,但是不能修改输入的数组。例如,如果输入长度为8的数组{2,3,5,4,3,2,6,7},那么对应的输出是重复的数字2或者3。
解决方法:
1. 辅助数组
昨天的题中,我使用了辅助数组的算法来实现找重复数,这次也是一样,我们不能修改原数组,可以创建一个大小为n+1的辅助数组,然后把原数组的每个数字放入到辅助数组中去,即原数组大小为m的数字应该放到辅助数组下标为m的位置上。由于这个算法使用了辅助数组,所以他的空间复杂度是O(n),代码如下:
// 不修改数组找出重复的数字 数字从1开始
public class day04 {
/***
* 利用辅助数组来实现
* @param arr 传入数组
*/
public int getDuplication(int[] arr) {
// 数组初始化全为0
int[] newArr = new int[arr.length];
for (int i = 0; i < arr.length; i++) {
// 参数的有效性检查
if (arr[i] <= 0 || arr[i] >= arr.length) {
throw new IllegalArgumentException("参数不正确");
} else {
if (newArr[arr[i]] == 0) {
newArr[arr[i]] = arr[i];
} else {
return arr[i];
}
}
}
return -1;
}
public static void main(String[] args) {
day04 test = new day04();
int[] arr = {2, 3, 5, 4, 3, 2, 6, 7};
System.out.println(test.getDuplication(arr));
}
}
// 得到的结果为8
2. 二分法
假设没有重复数,那么1~n之间,每个数都只能出现一次,而这个数组,肯定有重复数,所以至少有一个数出现的次数大于1。
我们把1~n的数字从中间数m开始分为两部分(二分),前一半为 1 ~ m, 后一半为 m+1 ~ n ,如果 1 ~ m中的数字在数组重出现次数大于m,那么这一半必定有重复的数字;否则,那么另一部分中必定含有重复数字;接着我们,继续对含有重复数字的区间一份为二,知道找到重复的数字。
代码如下:
public class day04 {
public static void main(String[] args) {
day04 test = new day04();
int[] arr = {2,2,2,3,4,5};
System.out.println(test.getDuplication2(arr));
}
public int getDuplication2(int[] arr) {
// 参数有效性检查
for (int i = 0; i < arr.length; i++) {
if (arr[i] < 0 || arr[i] >= arr.length) {
throw new IllegalArgumentException("参数不正确");
}
}
int start = 1;
int end = arr.length - 1;
while (end >= start) {
// 计算出中间位置
int middle = ((end - start) >> 1) + start;
int count = countRange(arr, start, middle);
if (end == start) {
if (count > 1) {
return start;
} else {
break;
}
}
if (count > (middle - start + 1)) {
end = middle;
} else {
start = middle + 1;
}
}
return -1;
}
private int countRange(int[] arr, int start, int end) {
if (arr == null) {
return 0;
}
int count = 0;
for (int i = 0; i < arr.length; i++) {
if (arr[i] >= start && arr[i] <= end) {
count++;
}
}
return count;
}
}
上述算法,长度为n的数组,countRange最多被调用O(logn)次,每次需要O(n)时间,所以总时间复杂度是O(nlogn)