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.
解法:使用鸽巢原理可以很简单的证明。注意Note的第四点,有且仅有一个重复的数字,但是这个重复的数字可能出现两次以上。这说明这n+1个数并不是一定包含了[1,n]中的每个数。开始时没注意到这点,用
return accumulate(nums.begin(), nums.end(), 0) - ((nums.size() - 1) * nums.size() >> 1);
结果显然错误。因为不能修改输入数组且空间复杂度要求为O(1),所以不能用排序,也不能使用HashTable。因为时间复杂度必须低于O(n^2),因此不能使用暴力破解法。
//修改输入数组的排序方法 class Solution { public: int findDuplicate(vector<int>& nums) { int n = nums.size(), i = 0; sort(nums.begin(), nums.end()); for(; i < n - 1; ++i) { if(nums[i] == nums[i + 1]) break; } return nums[i]; } };
//额外O(n)空间的Hash方法 class Solution { public: int findDuplicate(vector<int>& nums) { int n = nums.size(), i = 0; vector<int> cnt(n, 0); for (; i < n; ++i) { if (++cnt[nums[i]] > 1) break; } return nums[i]; } };
//O(n^2)时间的暴力破解法 class Solution { public: int findDuplicate(vector<int>& nums) { int n = nums.size(), i = 0, j = 0; for (i = 0; i < n - 1; ++i) { for (j = i + 1; j < n; ++j) { if (nums[i] == nums[j]) break; } if (j != n && nums[i] == nums[j]) break; } return nums[i]; } };
考虑到这n+1个数是在有序的[1,n]中选取的,因此可以考虑在[1,n]中使用二分查找,即在[1,n]中每次折半选出一个数mid,遍历输入数组统计小于等于mid的元素个数cnt。如果cnt>mid,说明重复值在[1,mid-1]之间,否则在[mid+1,n]之间。时间复杂度O(nlogn),空间复杂度O(1)。
class Solution { public: int findDuplicate(vector<int>& nums) { int n = nums.size(), left = 1, right = n - 1; while (left <= right) { int mid = (left + right) >> 1, cnt = 0; for (int i = 0; i < n; ++i) { if (mid >= nums[i]) ++cnt; } if (cnt > mid) right = mid - 1; else left = mid + 1; } return left; } };