首先,对于环形链表题,毫不犹豫,直接快慢指针(需要作解释的是,本题当然也可以使用哈希表进行解答,但在这里我们关注的是快慢指针的方法,并且快慢指针的空间复杂度也更低),具体做法为:
- 创建两个指针:快指针和慢指针,初始化都指向链表头节点
- 快指针每次走两步,慢指针每次只走一步
这样,如果链表存在环路,快慢指针终将会在环内的一个节点相遇(注意,不一定是环入口节点)。其实这个思想很好理解,就比如在一个环形跑道跑步,假设有两个人同时在起点,一个跑的快一些,一个跑得慢一些,并且两人都是匀速的,那么两人一定会在跑圈的一个地方再次相遇。这个思想就可以用于检测链表的环路。
本题的难点就是,如何寻找环的入口节点,因为快慢指针相遇的点并不一定是环入口节点。接下来,讲一下我对本题寻找环入口方法的理解:
- 将一个带环链表分为三部分:x = 从表头到环入口的节点个数;y = 从换入口节点到两指针相遇处的节点个数;z = 从相遇处到环入口的节点个数,其中很容易得到:y + z 为环的节点总个数
- 所以在慢指针和快指针相遇时,两指针构成的节点个数关系是:(慢)2 * (x + y) =(快)x + n * (z + y) + y。以上等式的解释:等式左边,因为快指针每次都比慢指针快两倍,所以是乘以2;等式右边,相当于走了x步到达入口处时,经过了n圈后,再从入口处走y步相遇
- 所以,x = (n - 1) * y + n * z = (n - 1)(y + z) + z。 以上等式说明了,从表头走到环入口节点的个数等于指针相遇点走 (n - 1)圈,再走z步的个数
- 注意,因为这里假设了链表存在环,所以 n>=1!
到此就解释了,为什么一个指针从表头开始遍历,另一个指针从从相遇点开始遍历,那么它们必定会相遇!
以下是本题的解答代码(java):
public class Solution {
public ListNode detectCycle(ListNode head) {
if(head == null || head.next == null) return null;
ListNode p = head, q = head;
//如果有环,找到一个环内的节点
while(q != null && q.next != null) {
p = p.next;
q = q.next.next;
if(p == q) break;
}
//该链表无环
if(q == null || q.next == null) return null;
//一个指针从链表头开始遍历,一个指针从环内的一个节点开始遍历,他们一定会在环的入口相遇。解释?
p = head;
//找环入口
while(p != q) {
p = p.next;
q = q.next;
}
return q;
}
}