题目:给定n+1个数,这个数的范围在1 ~ n间(包括1, n ) 且,有且仅有一个重复的数。找到这个重复的数。
(重复的数可以出现很多次,但只有一个重复的数)
一看这个题目:感觉方法是很多的,可以hash, 可以交换排序等。
但题目又给了很多约束条件:
1. 不能更改array
2. 只允许O(1)空间复杂度
3.时间复杂度不超过O(n^2)
有了这些约束,增加了这题的难度。
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.
在这里,介绍2种不同解法,第一种是基于binary search, 第二种是基于linklist 环的算法。
1.binary search解法
分析: 在1~n的范围内放了n+1个数,那么1~n范围是一个“拥挤”的状态。
假设mid = (1 + n ) / 2,有可能是 [1, mid] 拥挤,也有可能是(mid, n]拥挤(拥挤意味着有数重复出现)
那么什么时候[1, mid]拥挤呢? 当浏览array里面的数的时候 记录 <= mid的数的个数,记为count。
如果 count > mid, 那么是[1, mid] 部分拥挤。反而,是(mid, n]部分拥挤。
例如:我们的序列为4, 3, 7, 6, 2, 1, 2, 5 。 此时n = 7 , mid = 3, count = 4 (3,2,1,2都<= 3)
所以,拥挤的部分是在[1, 3]范围内,在这个范围内会有重复的数!
之后就是进行二分,找到最终重复的数。
复杂度:
时间复杂度 O(n log n)
空间复杂度O(1)
2.linklist 环的解法
这个思路借鉴了如何判断linklist有环,以及如何找到环的入口点。
例如序列:4, 3, 7, 6, 2, 1, 2, 5,我们把它想成linklist的形式
index 0 1 2 3 4 5 6 7
value 4 3 7 6 2 1 2 5
如下图A为起点, B为2者相遇点, C为环的入口点,即我们想要找的那个点。
复杂度:
时间复杂度 O(n)
空间复杂度O(1)
binarysearch的测试时间:
binarysearch的代码:
private int findDuplicateByBinarySearch(int[] nums) {
int left = 1, right = nums.length - 1, mid;// searching in value range from [left, right]
while (left < right) {
mid = left + (right - left) / 2;
int count = 0;
for (int num : nums) {// counting the number in [left, mid] part
if (num <= mid) {
count++;
}
}
if (count > mid) {//the value range in [left, mid] is to crowded.
right = mid;
} else {
left = mid + 1;
}
}
return left;
}
linklist circle测试时间:
linklist circle的代码:
private int findDuplicateByLinkListCircle(int[] nums) {
if (nums.length > 1) {
int slow = nums[0];
int fast = nums[nums[0]];
while (slow != fast) { //to find the meeting point
slow = nums[slow];
fast = nums[nums[fast]];
}
fast = 0;
while (slow != fast) {// to find the entrance point
slow = nums[slow];
fast = nums[fast];
}
return slow;
}
return -1;
}