题目描述
给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。
示例 1:
输入: [1,3,4,2,2]
输出: 2
示例 2:
输入: [3,1,3,4,2] 输出: 3
说明:
- 不能更改原数组(假设数组是只读的)。
- 只能使用额外的 O(1) 的空间。
- 时间复杂度小于 O(n2) 。
- 数组中只有一个重复的数字,但它可能不止重复出现一次。
题解1
如果没有数组只是只读的,空间只能是O(1)的限制,常用的方法有排序,然后判断相邻元素是否存在相等的(违背了数组是只读的原则)。或者用一个哈希表记录出现的数字,再次出现时,表明是重复元素(违背了空间只能是只读的原则)。除此之外,我没有想到空间为O(1)或数组只读的算法。
参考题解,二分查找法。
这个二分查找法不同于传统的二分查找,它的时间复杂度为 ,因为每次二分前,需扫描一次数组,统计不大于当前猜测值元素mid的元素的个数,如果个数大于猜测元素,表明重复元素位于[big, mid]区间,否则,重复元素位于[mid + 1, end]区间。
代码1
/*
二分法查找
时间复杂度为:O(nlogn)
空间复杂度为:O(1)
*/
class Solution {
public:
int findDuplicate(vector<int>& nums) {
int len = nums.size(), cnt = 0;
int left = 1, right = len - 1, mid = left + (right - left) >> 1;
while(left < right){
cnt = 0;
mid = left + (right - left) / 2;//mid为猜测值
for(auto num:nums){
if(num <= mid){//统计不大于mid的数的数量
++cnt;
}
}
if(cnt > mid){//重复的数出现在[left, mid]
right = mid;
}
else{//重复的数出现在[mid + 1, right]
left = mid + 1;
}
}
return left;
}
};
题解2
官方题解提供的Floyd判圈法。将原始问题转换为求链表的环的起点。
根据只有一个重复数字,且n+1个数字处于[1,n]的范围内,我们很容易构造这样一个链表。
链表的每一个节点 为nums[0],后一个节点为 ,则每i个节点为 ,因为nums数组中存在重复元素,必定会形成环。重复的元素即为环的起点
根据Floyd判圈法,我们很容易找到环的起点。
代码2
/*
快慢指针
时间复杂度为:O(n)
空间复杂度为:O(1)
*/
class Solution {
public:
int findDuplicate(vector<int>& nums) {
int slow = nums[0], fast = nums[0];//从起点出发
slow = nums[slow], fast = nums[nums[fast]];
//slow移动一点,fast移动两步
//找到两个相遇的位置
while(slow != fast){
slow = nums[slow];
fast = nums[nums[fast]];
}
//找到环的起点,即重复的元素
slow = nums[0];
while(slow != fast){
slow = nums[slow];
fast = nums[fast];
}
return slow;
}
};