给定一个单链表,只给出头指针h:
-
1、如何判断是否存在环?
-
2、如何知道环的长度?
-
3、如何找出环的连接点在哪里?
-
4、带环链表的长度是多少?
解法:
-
1、对于问题1,使用追赶的方法,设定两个指针slow、fast,从头指针开始,每次分别前进1步、2步。如存在环,则两者相遇;如不存在环,fast遇到NULL退出。
-
2、对于问题2,记录下问题1的碰撞点p,slow、fast从该点开始,再次碰撞所走过的操作数就是环的长度s。
-
3、问题3:有定理:碰撞点p到连接点的距离=头指针到连接点的距离,因此,分别从碰撞点、头指针开始走,相遇的那个点就是连接点。(附加:如果每个节点的元素值不重复,所有节点的元素值排序,第一个重复出现的节点就是。)
-
4、问题3中已经求出连接点距离头指针的长度,加上问题2中求出的环的长度,二者之和就是带环单链表的长度
1.判断单链表是否有环
使用两个slow, fast指针从头开始扫描链表。指针slow 每次走1步,指针fast每次走2步。如果存在环,则指针slow、fast会相遇;如果不存在环,指针fast遇到NULL退出。
就是所谓的追击相遇问题:
public class LinkListUtli {
class LNode{
//为了简化访问单链表,结点中的数据项的访问权限都设为public
public int data;
public LNode next;
}
public static boolean hasCircle(LNode L)
{
LNode slow=L;//slow表示从头结点开始每次往后走一步的指针
LNode fast=L;//fast表示从头结点开始每次往后走两步的指针
while(fast!=null && fast.next!=null)
{
slow=slow.next;
fast=fast.next.next;
if(slow==fast) return true;//p与q相等,单链表有环
}
return false;
}
}
证明:
首先,如果存在环,那么快指针和慢指针一定会同时进入到环中。假设当slow指针刚进入环时,fast指针所在的结点编号是a(现在只考虑环中的结点),slow指针所在结点编号是k+a,那么就环展开就像如下图所示:
由于slow和fast指针都是在环中的,所以可以将环展开的直线无限延伸。如果没有限制,slow和fast指针可以在环中无限的走下去。
假如说是fast和slow指针可以相遇,且相遇的位置为k+m+a(其中m表示slow指针进入环后走了m步就和fast指针相遇了),那么m一定是有解的。
我们简化一下,假设a的位置就是0:
由于slow指针从k结点走了m步就和fast指针相遇了,则一定有下面的等式成立:
k+m=2m
slow指针走了m步,会到达k+m结点处,相应的fast走了m步会到达2m结点,由于是两个指针走了m步相遇,所以一定有上面的等式成立。由等式可知,m一定是有解的,且m=k
0到k的距离和k到k+m的距离是相同的,而0到k+m是fast指针走的距离,正好slow的2倍。
也就是,如果没有环,一定不会相遇(fast一定会在前面,slow永远追不上);有环,一定会在k+m处相遇(将环无限延长n倍)。
2.求有环单链表的环长
在环上相遇后,记录第一次相遇点为Pos,之后指针slow继续每次走1步,fast每次走2步。在下次相遇的时候fast比slow正好又多走了一圈,也就是多走的距离等于环长。
设从第一次相遇到第二次相遇,设slow走了len步,则fast走了2*len步,相遇时多走了一圈:
环长=2*len-len。
3.求有环单链表的环连接点位置
第一次碰撞点Pos到连接点Join的距离=头指针到连接点Join的距离,因此,分别从第一次碰撞点Pos、头指针head开始走,相遇的那个点就是连接点。
在环上相遇后,记录第一次相遇点为Pos,连接点为Join,假设头结点到连接点的长度为LenA,连接点到第一次相遇点的长度为x,环长为R。
第一次相遇时,slow走的长度 S = LenA + x;
第一次相遇时,fast走的长度 2S = LenA + n*R + x;
所以可以知道,LenA + x = nR; LenA = nR -x; (理解:LenA = n*(R-1) 【保证回到Pos】 + n-x【保证剩下的距离正好到达Join】)
4.求有环单链表的链表长
上述2中求出了环的长度;3中求出了连接点的位置,就可以求出头结点到连接点的长度。两者相加就是链表的长度。
对快慢指针的理解:如果没有环,快指针一定先走完,两个指针不会相遇;如果有环,那么最多只有一个环,并且两个指针迟早都会进入环,假定环的长度为L,当慢指针进入环之后,快指针每次能向前追一步距离,最多只要L-1步就能追上慢指针所以可以用两个指针是否相遇来判断是否有环。