leetcode 287. 寻找重复数
问题描述
给定一个包含 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(N^2)\)的算法:
class Solution {
public:
int findDuplicate(vector<int>& nums) {
int n = nums.size(),i,j;
for(i = 0; i < n; i++)
{
for(j = i+1;j < n; j++)
{
if(nums[i] == nums[j])
return nums[i];
}
}
return -1;
}
};
结果:
执行用时 :2320 ms, 在所有 C++ 提交中击败了5.04%的用户
内存消耗 :10.9 MB, 在所有 C++ 提交中击败了11.92%的用户
代码2
给一个时间复杂度为\(O(N\log(N))\),但是移动数据的算法:
class Solution {
public:
int findDuplicate(vector<int>& nums) {
int n = nums.size(),i;
sort(nums.begin(),nums.end());
for(i = 0; i < n-1; i++)
{
if(nums[i] == nums[i+1])
break;
}
return nums[i];
}
};
结果:
执行用时 :28 ms, 在所有 C++ 提交中击败了10.50%的用户
内存消耗 :11.1 MB, 在所有 C++ 提交中击败了11.92%的用户
代码3
给一个时间复杂度为\(O(N)\)但是空间复杂度也是\(O(N)\)的算法:
class Solution {
public:
int findDuplicate(vector<int>& nums) {
int n = nums.size(),i;
vector<int> table(n,0);
for(i = 0; i < n; i++)
{
if(table[nums[i]] == 0)
table[nums[i]]++;
else
break;
}
return nums[i];
}
};
结果:
执行用时 :12 ms, 在所有 C++ 提交中击败了74.16%的用户
内存消耗 :10.9 MB, 在所有 C++ 提交中击败了11.92%的用户
代码4
下面给一个时间复杂度为\(O(N)\)空间复杂度为常数,但是要修改原数据的算法:
class Solution {
public:
int findDuplicate(vector<int>& nums) {
int n = nums.size(),i,index;
for(i = 0; i < n; i++)
{
index = abs(nums[i]) - 1;
if(nums[index] < 0)
break;
else
nums[index] *= -1;
}
return fabs(nums[i]);
}
};
结果:
执行用时 :20 ms, 在所有 C++ 提交中击败了23.46%的用户
内存消耗 :10.8 MB, 在所有 C++ 提交中击败了11.92%的用户
该方法和645. 错误的集合有类似之处。
代码5(二分法)
这是一个符合题意的算法,时间复杂度\(O(N\log(N))\),空间复杂度为\(O(1)\):
class Solution {
public:
int findDuplicate(vector<int>& nums) {
int n = nums.size(),i,num,left = 1,right= n,middle;
while(left < right)
{
middle = left+(right-left)/2;
num = 0;
//统计[1,middle]有多少个元素,如果元素个数少于等于middle,说明重复的元素在[middle+1,right]中,如果元素个数大于middle,说明重复元素在[left,middle]中
for(i = 0; i < n; i++)
if(nums[i] <= middle)
++num;
if(num <= middle) left = middle+1;
else right = middle;
}
return left;
}
};
结果:
执行用时 :36 ms, 在所有 C++ 提交中击败了8.16%的用户
内存消耗 :10.8 MB, 在所有 C++ 提交中击败了11.92%的用户
代码6(快慢指针)
这道题和160. 相交链表类似,时间复杂度为\(O(N)\),空间复杂度为\(O(1)\),思路如下:可以使用数组配合下标,抽象成链表问题。但是难点是要定位环的入口位置。举个例子:nums = [2,5, 9 ,6,9,3,8, 9 ,7,1],构造成链表就是:2->[9]->1->5->3->6->8->7->[9],也就是在[9]处循环。但是会在环内的[9]->1->5->3->6->8->7->[9]任何一个节点追上,不一定是在[9]处相碰。快指针每次走2步,慢指针每次走1步。设相遇时快指针走\(t_2\)步,慢指针走\(t_1\)步,环长为\(c\),因此\(t_2 = 2\times t_1\)。 则相遇时, 快指针比慢指针多走一个环的长度,即 \(t_2 = t_1 + c = 2t_1,t_1 = c\),即慢指针走了\(c\)步(恰好是环的长度),假设环之前的长度为\(m\)(也是要求的),则慢指针在环内走了\(c-m\)步,因此再让快指针从头开始出发,快慢指针一定在\(m\)处相遇。
class Solution {
public:
int findDuplicate(vector<int>& nums) {
int slow = 0,fast = 0;
do{
slow = nums[slow];
fast = nums[nums[fast]];
}while(slow!=fast);
fast = 0;
do{
slow = nums[slow];
fast = nums[fast];
}while(slow!=fast);
return slow;
}
};
结果:
执行用时 :24 ms, 在所有 C++ 提交中击败了14.95%的用户
内存消耗 :10.6 MB, 在所有 C++ 提交中击败了11.92%的用户