问题描述
Given an array nums containing n + 1 integers where each integer is between 1 and n (inclusive), prove that at least one duplicate number must exist. Assume that there is only one duplicate number, find the duplicate one.
Note:
You must not modify the array (assume the array is read only).
You must use only constant, O(1) extra space.
Your runtime complexity should be less than O(n2).
There is only one duplicate number in the array, but it could be repeated more than once.
问题分析
一个长度为n+1的数组,其中放了区间为[1,n]的数。当中有一个数出现了多次。其他值出现一次。让找出这个数。
没有限制条件,可以的方法是:统一每个数出现的次数。排序,找出连续相同的数。
体局中有一些限制:
空间复杂度是O(1)。
时间复杂度是O(n^2)
同时不能改变当前的数组。
这样就将之前的方法给排除了。
方法一
首先求出当前数的区间。[low,high].然后用中位数mid。然后判断[low,mid-1],[mid+1 , high]数的个数,如果两个都小于区间长度,那么mid就出现了多次。如果两个中有一个大于区间长度,那么就继续判断,知道区间 low = high。这个和二分法有点类似。使用了抽屉原理。
代码实现
public int findDuplicate(int[] nums) {
int max = Integer.MIN_VALUE;
int min = Integer.MAX_VALUE;
for (int num : nums) {
if (num > max) {
max = num;
}
if (num < min) {
min = num;
}
}
while (max > min) {
int mid = (min + max) / 2;
int leftCout = numnCount(nums, min, mid - 1);
int rightCount = numnCount(nums, mid + 1, max);
if (leftCout > (mid - min)) {
max = mid - 1;
} else if (rightCount > (max - mid)) {
min = mid + 1;
} else {
return mid;
}
}
return min;
}
/**
* 计算在这连个数之间的数的个数
*
* @param nums
* @param max
* @param min
* @return
*/
private int numnCount(int[] nums, int min, int max) {
int count = 0;
for (int num : nums) {
if (num >= min && max >= num) {
count++;
}
}
return count;
}
方法二
在一个数组中,如果有一个重合的数,如果类似于树一样遍历的话,会有环。
如果用两个指针,以一倍和两倍的速度往前移动,最终两倍的指针会追上一倍的指针。相遇的位置是在环中的某一个点上。这个时候,使用另外一个指针从数组头部开始,然后同时往前走,两个指针一定会相遇,而且相遇的位置是在环的入口的地方。,也是这个系统总相同的数的位置。可以证明一下的。
代码实现
public int findDuplicate(int[] nums) {
if (nums.length > 1) {
int slow = nums[0];
int fast = nums[nums[0]];
while (slow != fast) {
slow = nums[slow];
fast = nums[nums[fast]];
}
fast = 0;
while (slow != fast) {
slow = nums[slow];
fast = nums[fast];
}
return slow;
}
return -1;
}