2019-11-06 13:31:10 +00:00
# [287. Find the Duplicate Number](https://leetcode.com/problems/find-the-duplicate-number/)
# 思路
2019-12-09 08:58:30 +00:00
给定一个长度为n+1的只读数组, 里面所有的元素都位于[1, n], 所以很明显有重复元素, 题目假设只有一个重复数字( 但可以重复多次) , 求这个重复数字。要求空间复杂度为常数且时间复杂度小于O(n^2)。
2019-11-06 13:31:10 +00:00
2019-12-09 08:58:30 +00:00
## 思路一、二分法
2019-11-07 11:02:02 +00:00
题目说的时间复杂度要小于O(n^2), 那么我们可以想到比这个复杂度小一个量级的常见复杂度O(nlogn),由此可想到二分法。
由于重复值肯定是在[1, n]之间, 所以我们可以首先得到这个范围的中点mid, 然后遍历整个数组, 统计所有小于等于mid的数的个数count:
* 如果`count < = mid`, 则说明重复值一定是大于mid( 可以自己举例子理解) , 应进入右半部分继续二分;
* 否则, 重复值不大于mid。
此时时间复杂度就是O(nlogn)
2019-12-09 08:58:30 +00:00
## 思路二、位操作
由于要求常数空间复杂度, 所以就不能用hash什么的; 然后要求时间复杂度小于O(n^2),暴力计数也不行。既然直接计数不行,那么我们可以考虑位操作:
计算32位中每一位当中1出现的次数。具体来讲, 对于第i位( 0 < = i < = 31) , 我们用count1代表整个nums数组中所有元素第i位是1的个数,
用count2代表0~n这些数中第i位是1的个数。若 count1 > count2, 说明最终所求的重复元素在第i位是1, 否则是0。这样我们就可以通过分别计算32位1出现的次数来得到结果。
时间复杂度为O(n)
2019-11-07 11:02:02 +00:00
## 思路三
此题还有一个十分巧妙的方法[可参考[此处](https://leetcode.com/problems/find-the-duplicate-number/discuss/72846/My-easy-understood-solution-with-O(n)-time-and-O(1)-space-without-modifying-the-array.-With-clear-explanation.)]。此时要把nums数组看成一个链表: `nums[i] = j`表示链表中结点i的next结点为j, 例如数组
```
index : 0 1 2 3 4
nums[i]: 3 1 3 4 2
```
表示的链表为
```
0 -> 3 -> 4 -> 2
^ |
|_________|
```
可以看到这个链表是有环的, 为什么有环呢, 就是因为数组nums中存在重复元素3。仔细分析我们可以发现, 链表中环的开始结点即为重复元素,
即题目转变为求一个带环链表中环的开始结点,这其实就是[142. Linked List Cycle II](https://leetcode.com/problems/linked-list-cycle-ii/),可参见这题我的[题解](https://github.com/ShusenTang/LeetCode/blob/master/solutions/142.%20Linked%20List%20Cycle%20II.md),这里直接给出代码。
2019-12-09 08:58:30 +00:00
## 若可改变数组
本题要求数组nums是只读的, 即不能改变nums, 如果可改变nums, 那么此题还有一个时间复杂度O(n)空间复杂度O(1)的解法( 剑指offer第二版面试题3) 。
由于所有元素都在区间[1, n]之内, 所以如果没有重复的数字, 那当数组排序后数字m会排在第m位( 即下标是m-1) 。
所以我们可以重排这个数组。用while循环从头到尾扫描这个数组, 当扫描到nums[i]时,
1. 如果`i == nums[i] - 1`, 则这个数字已经在它该在的位置, i加1然后进入下一循环即可;
2. 否则,如果`nums[i] == nums[nums[i]-1]`,即`nums[i]`出现了两次,找到了重复值,返回即可。
3. 否则,即`nums[i] != nums[nums[i]-1]`,应该交换`nums[i]`和`nums[nums[i]-1]`, 进入下一循环( i不加1) 。
因为调用swap的次数是线性的, 所以时间复杂度还是O(n), 而没有引入额外的数组, 所以空间复杂度是O(1)。
2019-11-06 13:31:10 +00:00
# C++
2019-11-07 11:02:02 +00:00
2019-12-09 08:58:30 +00:00
## 思路一
2019-11-07 11:02:02 +00:00
``` C++
class Solution {
public:
int findDuplicate(vector< int > & nums) {
int n = nums.size() - 1;
int low = 1, high = n;
while(low < high ) {
int mid = low + (high - low) / 2;
int count = 0;
for(auto & num: nums)
if(num < = mid) count++;
if(count < = mid) low = mid + 1;
else high = mid;
}
return low;
}
};
```
2019-12-09 08:58:30 +00:00
## 思路二
``` C++
class Solution {
public:
int findDuplicate(vector< int > & nums) {
int res = 0, mask = 1, n = nums.size() - 1;
for(int i = 0; i < 32 ; i + + ) {
int count1 = 0, count2 = 0;
for(int j = 0; j < = n; j++){
if(mask & nums[j]) count1++;
if(mask & j) count2++;
}
if(count1 > count2) res += mask;
if(i < 31 ) mask < < = 1 ;
}
return res;
}
};
```
2019-11-07 11:02:02 +00:00
## 思路三
``` C++
class Solution {
public:
int findDuplicate(vector< int > & nums) {
int slow = 0, fast = 0;
while(true){
slow = nums[slow];
fast = nums[nums[fast]];
if(slow == fast) break;
}
int p = 0;
while(true){
if(p == slow) break;
slow = nums[slow];
p = nums[p];
}
return slow;
}
};
```
2019-12-09 08:58:30 +00:00
## 若可改变数组
``` C++
class Solution {
public:
int findDuplicate(vector< int > & nums) {
int n = nums.size() - 1;
int i = 0;
while(i < = n){
if(i == nums[i] - 1) i++;
else if(nums[i] != nums[nums[i]-1]) swap(nums[i], nums[nums[i]-1]);
else return nums[i];
}
return 0;
}
};
```