题目描述
给定一个长度为 n+1 的数组nums,数组中所有的数均在 1∼n 的范围内。
请找出数组中任意一个重复的数,但不能修改输入的数组。
给定 nums = [2, 3, 5, 4, 3, 2, 6, 7]。
返回 2 或 3。
思路
1.暴力法
参考3-1链接:数组中重复的数字
2.辅助数组
创建一个长度为n+1的辅助数组,如果原数组中被复制的数字是m,就把它度知道辅助数组的下标为m的位置。
public static Integer findDuplicateInArray(int[] a){
int[] newA = new int[a.length];
for (int m : a) {
if (newA[m] == m) return m;
newA[m] = m;
}
return -1;
}
public static void main(String[] args) {
int[] a = new int[]{2, 3, 5, 4, 3, 2, 6, 7};
System.out.println(findDuplicateInArray(a));
}
3.二分法
将数组长度二等分,计算其中一半的数字在数组中出现的次数,如果次数大于一半说明这一半数字中必有重复。否则,另一半数字中出现重复数字。
举例:[2, 3, 5, 4, 3, 2, 6, 7]长度为8,分为1-4,5-8两部分。计算数字1,2,3,4在这个数组中出现的总次数为5次。所以1-4中必有重复,同理拆分为1-2和3-4。最后找出重复数字
public static Integer findDuplicateInArray2(int[] a){
if(a == null || a.length <= 0) return -1;
int start = 1;
int end = a.length - 1;
while(start <= end){
int mid = start + (end - start) >> 2;
int count = countRange(a, start, mid);
if(end == start){
if(count > 1) return start;
else break;
}
if(count > mid - start + 1) end = mid;
else start = mid + 1;
}
return -1;
}
private static int countRange(int[] a, int start, int end) {
int count = 0;
if(a == null) return count;
for (int value : a) {
if (value >= start && value <= end) {
++count;
}
}
return count;
}
public static void main(String[] args) {
int[] a = new int[]{2, 3, 5, 4, 3, 2, 6, 7};
System.out.println(findDuplicateInArray2(a));
}
countRange会被调用O(logn)次,每次是O(n),因此总的时间复杂度为O(nlogn)
这种二分法不能保证找到所有的重复数字:[2,3,5,4,3,2,6,7]中的2就不能找出来,因为1-2在数组中出现了两次。