这个问题,首先应判断两个单链表是否含有环,判断是否有环的方法很多,目前最好的方法是:一开始设置两个指针都指向表头,其中一个每次(一步)前进一个节点的叫p1,另外那个每次(一步)前进两个节点的叫p2 。p1和p2同时走,当其中有一个遇到null,就证明链表没有环。如何某个时刻(假设走了n步之后),p1和p2指向的地址相同,那么链表就是有环的。
方法代码如下:
public boolean isLoop(Node h){
Node p1 = h;
Node p2 = h;
while(p2.next != null && p2.next.next != null){
p1 = p1.next;
p2 = p2.next;
if(p1 == p2) break;
}
return !(p1==null||p2==null);
}
这样,就判断出两个链表是否存在环,有以下3种情况,一、两链表均不含环,此时为情况一;二、两链表均含环,此时为情况二;三,一个有环一个无环,显然两链表此时不可能相交,该情况不考虑。
一、均无环的情况下判断是否相交,且找出第一个相交点
无环情况下,判断是否相交,可以让两个链表均遍历至尾节点,如果尾节点相同,则说明两链表是有交点的,代码如下:
/*
* 无环情况下判断是否相交 两个链表无环时若相交,则至少尾节点必然是同一个
*/
public boolean isJointNoLoop(Node h1, Node h2) {
Node p = h1;
Node q = h2;
while (p.next != null) {
p = p.next;
}
while (q.next != null) {
q = q.next;
}
return p == q;
}
此时已判断出是否相交,相交后,来寻找第一个相交点,此时有两种思路:方法一:较易懂,算出两个链表的长度差δ,两个指针从表头出发,之后较长的链表先移动δ步,之后两链表同时移动,直到遇到相同的节点,该节点即为第一个相交点,代码如下:
/*
* 无环情况下找出第一个相交点
*/
public Node getFirstJoint(Node h1, Node h2) {
int len1 = 0;
int len2 = 0;
while (h1.next != null) {
len1++;
h1 = h1.next;
}
while (h2.next != null) {
len2++;
h2 = h2.next;
}
return len1-len2>=0?getNode(h1,h2,len1,len2):getNode(h2, h1, len2, len1);
}
private Node getNode(Node h1, Node h2, int len1, int len2) {
int i = 0;
while (i < len1 - len2) {
h1 = h1.next;
}
while (true) {
h1 = h1.next;
h2 = h2.next;
if (h1 == h2)
return h1;
}
}
方法二:将其中一个链表首尾相连,检测另外一个链表是否存在环,如果存在(该环就是首尾相连的链表),则两个链表相交,而检测出来的依赖环入口即为相交的第一个点。需找出环的入口,设置p1,p2两个指针,同样一个走一步一个走两步,两者相遇则必在环上某一点相遇,记下此位置p1=p2,在p1和p2重合后,设置一个p3指向表头,然后p1和p3每次同时行走一步,每步前进一个节点,等到p1和p3重合时,重合的位置就是环的入口。
可以这样理解,如图:
设L1为无环长度,L2为环长,a为两指针相遇时慢速指针在环上走过的距离,而且a一定小于环总长L2(这是因为当慢速指针刚进入环时,快速指针已经在环中,且距离慢速指针的距离最长为L2-1,需要追赶的距离为L2-1,即刚好在慢速指针的下一个节点,需要几乎一整圈的距离来追赶,赶上时,慢速指针也不能走完一圈)。此时设慢速指针走过的节点数为N,则可列出:
快速指针走过的节点数为: 2N = L1 + k * L2 + a; (这里快速指针走过的节点数一定是慢速指针走过的2倍)。
慢速指针走过的节点数为: N = L1 + a;
则相减可得, N = k * L2 , 于是得到 k * L2 = L1 + a; 即, L1 = (k-1) * L2 + (L2 - a) (这里k至少是大于等于1的,因为快速指针至少要多走一圈)
即 L1的长度 = 环长的整数倍 + 相遇点到入口点的距离, 此时设置头结点p3, 与p1同时,每次都走一步,相遇点即为入口点。
整个过程代码如下:
/*
* 相交的两个不含环单链表的第一个交点,方法一:将其中一个链表首尾相接,此时可看作一个含环单链表,找出环入口点即可
*/
public Node entryNoLoop(Node h1, Node h2) {
Node p = h1;
while (p.next != null) {
p = p.next;
}
// 此时p指向链表1的尾节点,首尾相连
p.next = h1;
return entryLoop(h2);
}
/*
* 找出一个含环单链表的环入口点
*/
public Node entryLoop(Node h) {
Node p3 = h;
Node p1 = h;
Node p2 = h;
while (p2.next != null && p2.next.next != null) {
p1 = p1.next;
p2 = p2.next.next;
if (p1 == p2) {
break;
}
}
while (p3 != p1) {
p1 = p1.next;
p3 = p3.next;
}
return p3;
}
二、均有环的情况下,判断是否相交
思路与无环时的思路较类似,二者均有环,那么可以找出各自环的入口点,然后从各自的入口点出发,一个快速(每次2步),一个慢速(每次1步),直到相遇,说明相交。