简介
简单来说,就是在链表里,慢指针一次走一步,快指针一次两步。
题目
在一个长为n+1的数组a中,每个数组元素ai:1 <= ai <=n.
并且,只有一个重复出现的数字,找出这个数字。
原数组不可动。
其实挺简单的,空间复杂度O(n)时,时间复杂度O(n)的方法是比较容易想得到的。
但是今天我学到一种可以时间复杂度O(n),但是空间复杂度O(1)的–借助快慢指针。
分析
客观条件:数组值有重复,下标肯定不重复
所以,换一下遍历的规则:
1.令p1=0:p1=a[p1]
2.令p2=0:p2=a[a[p2]]
其中p1是慢指针,p2是快指针。(p2走了一圈,p1还在中点的那种快和慢)
将数组值考虑为链表中结点编号,遍历的顺序描述为结点间关系(谁能到达谁),
假设现有列表[2,1,5,4,6,6,3],那么由上述可得链表:
p1,p2都会在这个有环链表中一直走下去,区别是一个一次1步,一个一次2步。
那么此题目中重复的数字会是“割点”(割点是图论中的概念,这里暂且一用)的编号(图中的6),那么如何找到这个割点呢?
接下来就是数学证明啦!
p1、p2在一个有环链表中这样走的话,肯定最终相遇在环上的某点。
为了证明,先抛开上述条件,假设在这样一个链表中:
其中,0为公共起点,第x点为“割点”,第x+t点为p1、p2最终相遇点。假设环长为y(环上有y个结点)
那么p1在相遇时走了x+t+ky步,p2走了x+t+k’y步,其中k>=0,k’>=1,且k<k’.
由于p1、p2脚程是两倍关系:(x+t+ky)*2=x+t+k’y
得出一个小结论,记作Q:x+t=ky (将上面的2k-k’记作k,取正整数),即x+t会是y的正整数倍。
我们的目的是找出x:
当p1、p2相遇时,p1步数是x+t+ky,p1只要再走y-t+hy步(h为整数),便是x点;
假设p3在p1、p2相遇时,就在0点了,此后p1每走一步,p3也走一步,那么p3走x+h’y(h’为整数)步后,也可以到达x点;
并且此时 h>=h’ .
假设二者能够相遇的话,那么 y-t+hy = x+h’y 得出:
x+t=ky (k为正整数),即是上述的结论Q,也就证明了二者真的能够相遇。
所以在p1、p2相遇时,只要p3位于0点,接着p1走一步,p3也走一步,那么二者相遇处的结点编号即为重复出现的数值。
大概代码:
var a=[2,1,5,4,6,6,3];
let p1=0,p2=0,p3=0;
do{
p1=a[p1];
p2=a[a[p2]];
}while(p1!=p2);
do{
p1=a[p1];
p3=a[p3];
}while(p1!=p3);
console.log(p3);//6
可见时间复杂度O(n),而空间复杂度只是O(1)。
题外话
不过这道题如果数组元素大小没有限制,然后也是只有一个重复的话,这种解法就用不了了。
如果没有“原数组不可动”这一限制的话,可以排序后遍历,是O(nlogn)的复杂度。
但是也是限制了,那么便可以二分枚举数值了。
大概这样: