题目描述
分析实现 法一
若不考虑空间复杂度必须为O(1)的条件,在满足1、3、4的条件下,对于找寻重复的数字来说,很简便的一种方法就是映射。将元素值作为新开数组的下标,进行出现次数的统计。
class Solution {
public:
int findDuplicate(vector<int>& nums) {
int len=nums.size()+1;
vector<int> log(len,0);
int result=0;
for(int i=0;i<len;i++){
log[nums[i]]++;
}
for(int i=0;i<len;i++){
if(log[i]>1){
result=i;
break;
}
}
return result;
}
};
分析实现 法二
现在,我们需要考虑空间复杂度必须为O(1)的条件,也即需要写出一个满足上述四个条件的代码,怎么入手呢?回顾法一,其成功在于以重复数字作为下标的元素被累加了两次及其以上,我们找到元素值大于1的,也就找到了重复数字。可是现在,不能创建个以N为大小的数组呀,我们怎样才能抓住下标元素被累加到了两次及其以上呢?
因为有重复的数字,那么对于value初始化为0,value = nums[value]来说,value会最终陷在一个循环里。如nums={1,3,4,2,2},那么value=1,3,2,4,2,4,2,4,…..。而这个循环里,就有我们的重复数字。
那现在问题就变为如何发现这个循环,并且如何从循环里拿到重复数字。所以便有了下面的代码:
class Solution {
public:
int findDuplicate(vector<int>& nums){
if (nums.size() > 1){
//开始起跑,slow一步一步跑,fast两步并一步跑
int slow = nums[0];
int fast = nums[nums[0]];
//确定它俩陷在了循环里
while (slow != fast){
slow = nums[slow];
fast = nums[nums[fast]];
}
//fast重新开始一步一步地起跑,当与陷入循环里的slow相遇时,即为所求
fast = 0;
while (fast != slow){
fast = nums[fast];
slow = nums[slow];
}
return slow;
}
return -1;
}
};
法2 细节说明
这里我们引入另一个加速的循环,value = nums[nums[value]],如此,通过两个循环的碰撞便能发现了循环并拿到重复的元素。那为什么这两个循环一定会相撞呢?我们用物理学里的速度路程(s=vt)来论证。
我们把慢的循环叫slow,快的循环叫fast。因为是循环永无止境,所以追踪的路程s可以无限大。
现在,想让fast追上slow,fast的速度为vf,slow的速度为vs(vs结合本题的意思就是value = nums[value]计算一次),并且vf=2*vs。我们可以列出公式,vs*t=vf*t+dis。其中dis为一开始slow和fast相隔的距离。
从而,我们可以解出t=dis/vs。
现来看dis的取值,slow和fast最多相距n的距离。综上,它们一定会相撞。因为从物理学中知道,路程无限的条件下,快速度的一定会追上慢速度的。并且只要在O(n)的时间复杂度下,我们就能让两个循环相撞了。
分析实现 法3
之所以会想到法2,是受法1映射的思想影响。现在,我们从中跳出来,看看还有没有其他的办法。分治法。
我们要充分利用题目给的条件,“Given an array nums containing n + 1 integers where each integer is between 1 and n (inclusive)”
n+1个数,每个数的值还只能是从1到n中取,那么我随便说一个1到n中的数,如果数组中小于等于它的数的个数竟然大于该数值本身,那么肯定是有重复的。
举个例子,nums={4,3,6,5,4,1,2},我随便说个数:5,那么nums中小于等于5的元素个数为6,说明Duplicate Number的值比5小,就是因为Duplicate Number,才会造成数量反超的局面。
代码实现如下,使用二分搜索连缩小范围,
class Solution { public: int findDuplicate(vector<int>& nums){ int len=nums.size(); int low=1; int high=len-1; int mid,count; while(low<high){ mid=low+(high-low)/2; count=0; for(int i=0;i<len;i++){ if(nums[i]<=mid) //统计小于mid的元素个数 count++; } if(count<=mid) low=mid+1; else //说明有元素重复了不止一次 high=mid; } return low; } };