题目描述:给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出 null 。
解题思路: 可以直接暴力求解,依次将链表中的结点放入一个 List 中,第一个重复的结点就是入口结点,下面给出两种比较巧妙的方法,思想值得学习。
解法一: 双指针。上一题「链表中倒数第 k 个结点」中,利用双指针的思想进行求解,此题同样可以借用这一思路。假设链表长度为 N ,第 N 个结点连接到第 k 个结点形成了环,这样链表中环的入口结点就转化成了倒数第 N-k+1 个结点,N-k+1 即为环中结点的数量,求出环中结点的数量就能计算出入口结点,怎么计算环中结点数量呢?
使用快慢指针,慢指针一次走一步,快指针依次走两步,如果走的过程中快指针追上了慢指针,说明链表中有环(因为在两个指针都进入环的时候,一个步长为 1,一个步长为 2,两个指针之间的距离每走一步就缩小 1 个单位,所以两个指针肯定会再次相遇),并且相遇的位置一定在环内,从相遇结点触发再回到相遇结点的距离就是环的长度。
public ListNode EntryNodeOfLoop(ListNode pHead){
// 寻找相遇结点
ListNode meetingNode = MeetingNode(pHead);
if(meetingNode == null) return null;
// 计算环中的结点数
int nodesInLoop = 1;
ListNode nowNode = meetingNode;
while(nowNode.next != meetingNode){
nowNode = nowNode.next;
nodesInLoop++;
}
// 快指针前移
ListNode slow = pHead, fast = pHead;
while(nodesInLoop > 0){
fast = fast.next;
nodesInLoop--;
}
while(fast != slow){
slow = slow.next;
fast = fast.next;
}
return slow;
}
// 寻找相遇节点
public ListNode MeetingNode(ListNode pHead){
if(pHead == null) return null;
ListNode slow = pHead.next;
if(slow == null) return null;
ListNode fast = slow.next;
while(fast != null && slow != null){
if(fast == slow) return fast;
slow = slow.next;
fast = fast.next;
if(fast != null) fast = fast.next;
}
return null;
}
解法二: 数学推导。此方法解释来自牛客网大佬题解。
假设 x 为环前面的路程(黑色路程),a 为环入口到相遇点的路程(蓝色路程,假设顺时针走),c 为环的长度(蓝色+橙色路程)。
当快慢指针相遇的时候:
慢指针走过的路程为 S_slow = x + m * c + a
;
快指针走过的路程为 S_fast = x + n * c + a
;
2 * S_slow = S_fast
2 * (x + m * c + a) = (x + n * c + a)
从而可以推导出:
x = (n - 2 * m) * c - a = (n - 2 * m - 1) * c + c - a
即环前面的路程 = 数个环的长度(可能为 0 ) + c - a
什么是 c - a
? 这是相遇后,环后面部分的路程(橙色路程)。
所以,可以让一个指针从起点 A 开始走,让一个指针从相遇点 B 开始继续往后走,两个指针速度一样,那么,当从原点的指针走到环入口点的时候,从相遇点开始走的那个指针也一定刚好到达环入口点。
public ListNode EntryNodeOfLoop(ListNode pHead){
if(pHead==null || pHead.next==null || pHead.next.next==null)return null;
ListNode fast = pHead.next.next;
ListNode slow = pHead.next;
//先判断有没有环
while(fast!=slow){
if(fast.next!=null && fast.next.next!=null){
fast=fast.next.next;
slow=slow.next;
}else{
return null;
}
}
//循环出来的话就是有环,且此时fast==slow.
fast=pHead;
while(fast!=slow){
fast=fast.next;
slow=slow.next;
}
return slow;
}