最近一直在无脑敲代码,结果今天下午被一个准备各种面试题的同学问了一个带环单链表的问题,发现最近的智商真心不够用,想了稍长时间,最后算是想明白了。因为最后想到头大的时候,百度了一下,下面就建立在自己思考的基础上,讲解一下,如又不对的地方,望指正。
首先,对于一个n节点的单链环,如果给定一个开始节点n0,然后用a,b两个指针,分别以1和2的步长遍历,那么在a环了多少圈(M),a距离n0有一个怎么样的偏移量(offset)的情况下,a,b会重合呢?即指向同一个节点。
>>>让我们跳过费神的数学推导证明什么的,先给出答案吧。在a走完一圈之后,a,b会回到n0节点,也即a,b重合,所以M=1,offset = 0。
为什么?
>>>有些人可能想当然的认为,重合点的偏移量和圈数可能是随机的(好吧,至少我同学一开始是这么认为的)。不要被上面给所谓的1和2的步长分神了,其实是这样的,对于整数N(>=1)和2N,显然这两个数的最小公倍数是2N,不可能是2N+X,粗略说明偏移量是0 ,而最大公约数是N本身,粗略说明圈数是1。。。换个简单的方式说:一圈之内,a,b必不重合,如果节点数n为偶,那么b刚达到一圈时,a恰巧在中间点(前一半的最后一点)。虽然b的增量大,奈何a领先于b。a接下来面对的是n/2个节点,而b面对的是n个节点,所以在移动n/2次后,a和b重合在出发点;如果节点数n为奇数,那么n-1,n+1必为偶数,第一圈考虑n-1为环长,当b节点到达n-1点时,将环长考虑为n+1,则同样可证a和b会重合在出发点。
其次,基于上面的结论,对于一个n节点的单链环,当a和b从环上不同的节点开始以1和2的步长遍历时,a和b是否会有重合的时候?
>>>一开始我和我的同学想当然的认为会重合,因为考虑这是个面试题,不会那么诡异,所以感觉会重合。但如果要证明呢? 利用上面的结论!有人可能会怀疑上面的重合的逆过程不能穷举所有的这里的可能性。实际上这不同太担心。 可以这样考虑。从出发点出发,每移动一次,因为增量上b是2,a是1,所以a和b之间的距离会随着移动的次数逐次加1。而总体的过程,b移动2N个点,a移动N个点,所以完全可以制造出[0, N)(半开半闭区间)的初始距离。 综上所述,会重合。
接下来,对于一个共有L(未知)个节点的带环单链表,其中属于环部分的节点数为L0(未知),非环部分为L1(未知)那么如何知道环的入口节点呢?
>>>首先,其实这里的L0是可知的。基于前面两条,当你从链表头指针处,用a和b两个指针按照1和2的步长遍历链表时,在a和b首次重合时,可以激发这样一个操作,那就是让a和b继续遍历并计数,在一下次重合的时候就可以知道环长了,也就是L0。
>>>然后,这里给一个链接http://www.douban.com/note/172176904/,在这之前我一直属于神YY状态,没怎么用公式,所以到这里的时候卡住了,现在巧借前人智慧,继续说明。PS:链接里面的内容,我也有点怀疑作者有没有彻底思考,但看了人家的东西才幡然开窍却是事实。
设:a经过的节点数为 la = L1 + offset,lb = L1 + offset + N*L0 =2(L1 + offset)。(这里不同于链接中的内容。如果你接受了我前面所说的a只需要遍历一圈就可以和b重合,那么这里就无需多说了)
则:L1 + offset = N * L0。
基于上式,在求得L0的基础上,再利用链接里说的,重合后,b重新从头节点处以1的步长遍历(这次变成1了),那么在接下来的重合的时候,就是环的入口节点了。因为步长都变成1了,最后offset的部分都是同时遍历的。
至此,问题结束。
-----------------------------更新--------------------------------
还有一个稍有意思的面试题:怎样求两个单链表的第一个重合节点?
对于这个题,可以在纸上画一个Y,只是Y上面的两个分支不等长而已。对于其他的解法,我就不多说了,现在基于上面所说的带环单链表的方法,说一下这个解重合节点的方法。如果“Y”上面的两个节点分别A,B,下面的那个节点为C,第一个重合节点为X,那么就是如何求出X了。
无论如何,A,B,C三个节点是必然可知的。我们将A,B中的任意一个节点设置为C的next,这样这个有重合的两个点链表就变成了一个带环单链表,X也就变成了环的入口节点。用上面的方法,在求出X后,再将C的next设置为null就可以了。我只是利用了一下尾节点的next,它本来是指向null的,这样稍加利用,也应该无伤大雅。
这不科学吗?想要证明吗?自己在纸上画画就得了。
这下,我把两个题目穿到了一块,所以两个题目的解法在某种意义上都可以互用了。