参考博客:https://segmentfault.com/a/1190000003817671
里面介绍很多方法,而且都给出空间和时间复杂度。
选用最后一种方法,学到新算法。Floyd's Algorithm
把整个数组看成是数组下标到数组元素的映射,如果有重复元素的话,由映射关系形成
的链表会形成环。如引用原博客的解释:
假设数组中没有重复,那我们可以做到这么一点,就是将数组的下标和1到n每一个数一对一的映射起来。
比如数组是213,则映射关系为0->2, 1->1, 2->3。假设这个一对一映射关系是一个函数f(n),其中n是下标,
f(n)是映射到的数。如果我们从下标为0出发,根据这个函数计算出一个值,以这个值为新的下标,再用这个函数计算,
以此类推,直到下标超界。实际上可以产生一个类似链表一样的序列。比如在这个例子中有两个下标的序列,0->2->3。
但如果有重复的话,这中间就会产生多对一的映射,比如数组2131,则映射关系为0->2, {1,3}->1, 2->3。
这样,我们推演的序列就一定会有环路了,这里下标的序列是0->2->3->1->1->1->1->...,而环的起点就是重复的数。
所以判断是否有重复元素就可以转换成判断链表是否形成环,寻找数组里面的重复元素(只有一个)可以转换成
查找链表中环的起点。
可以用Floyd's Algorithm的龟兔赛跑解答:
首先设环的起点是m,环的长度是l,乌龟和兔子第一次相遇的地点到环起点的距离是k;
乌龟和兔子都是从链表的起点出发,兔子的速度是乌龟的两倍,如果链表存在环,很容易想到,兔子和乌龟总会在环里的
某个节点相遇,这时候
乌龟移动了i步,存在关系i = m + a * l + k (a是乌龟绕着环跑的圈数);
兔子移动了2i步,存在关系2i = m + b * l + k (b是兔子绕着环跑的圈数);
根据这两个关系可以得到关系:m + k = (b - 2a) * l;
根据这一个关系可以设计第二轮赛跑,第二轮赛跑乌龟和兔子的速度相等,但是起点不同,乌龟的起点是链表的起点,
兔子的起点是m+k,当它们再次相遇的时候,这个节点就是环的起点。
对第二轮的赛跑的解释:
兔子的起点是m + k,乌龟从链表的起点到环的起点共走了m步,那么兔子也走了m步,根据关系m + k = (b - 2a) * l,
兔子再走k步就能绕环转整数倍,所以兔子此时的位置是在它的起点往后倒退k步,所以兔子的终点就是m,即环的起点。
class Solution {
public:
int findDuplicate(vector<int>& nums) {
int tortoise = nums[0], hare = nums[nums[0]];
while (tortoise != hare) {
tortoise = nums[tortoise];
hare = nums[nums[hare]];
}
//tortoise = nums[0];
//hare = nums[hare];
tortoise = 0; //虽然这里不存在环的起点的case,但是还是这样写比较好
while (tortoise != hare) {
tortoise = nums[tortoise];
hare = nums[hare];
}
return tortoise;
}
};