题目:如果一个链表中包含环,请找出该链表的环的入口结点。
思路一:
首先申请两个指针,快指针一次前进两步,慢指针一次前进一步,初始化都再链表头部。然后开始移动,如果他们指向了同一个节点,则证明有环,否则没环。当他们指向了同一个节点时,慢指针再次初始化,指向头结点。快慢指针前进步数都改为1,当他们再次指向同一个节点,这个节点就是环的入口节点。
讨论:单链表中的环的问题还有许多扩展,比如求环的长度,或者是如何解除环等等,可参见网上大神的这个总结贴关于单链表存在环的讨论。
基于以上思路,java参考代码如下:
package chapter3;
import chapter2.ListNode;
import zuo_chapter2.P77_RemoveRepeat;
public class P139_MeetingNode {
public static class ListNode{
int val;
ListNode next;
public ListNode(int val){
this.val=val;
this.next=null;
}
}
public static ListNode meetingNode1(ListNode head){
if(head==null||head.next==null)
return null;
ListNode fast=head,slow=head;
while(fast!=null&&fast.next!=null){
slow=slow.next;
fast=fast.next.next;
//if(slow!=null&&slow==fast)//注意点1:不需要单独讨论slow,因为fast在slow前面,讨论fast是否为空即可
if(slow==fast) break;
}
if(fast==null||fast.next==null) return null;//注意点2:没有环,则直接返回null。
slow=head;
while(slow!=fast){
slow=slow.next;
fast=fast.next;
}
return slow;
}
//新增解法,自我感觉更简洁
public static ListNode meetingNode(ListNode head){
if(head==null) return null;
ListNode fast=head,slow=head;
do{
fast=fast.next;
if(fast==null)
return null;
fast=fast.next;
slow=slow.next;
}while (fast!=null&&fast.next!=null&&fast!=slow);//只能是fast==slow退出,空的情况前面if(fast==null)已经跳出。
fast=head;
while (fast!=slow){
fast=fast.next;
slow=slow.next;
}
return slow;
}
public static void main(String[] args){
ListNode head = new ListNode(1);
head.next= new ListNode(2);
head.next.next = new ListNode(3);
head.next.next.next = new ListNode(4);
head.next.next.next.next = new ListNode(5);
head.next.next.next.next.next = new ListNode(6);
head.next.next.next.next.next.next =head.next.next ;
System.out.println(meetingNode(head).val);
System.out.println(meetingNode1(head).val);
}
}
注意点见代码注解部分。
相关题目:求此环的长度。
设:链表头是X,环的第一个节点是Y,slow和fast第一次的交点是Z。各段的长度分别是a,b,c,如图所示。
第一次相遇时slow走过的距离:a+b,fast走过的距离:a+b+c+b。
因为fast的速度是slow的两倍,所以fast走的距离是slow的两倍,有 2(a+b) = a+b+c+b,可以得到a=c(这个结论很重要!)。
我们发现L=b+c=a+b,也就是说,从一开始到二者第一次相遇,循环的次数就等于环的长度。
java参考代码如下:
package chapter3;
public class P139_MeetingNodeOfLength {
public static class ListNode{
int val;
ListNode next;
public ListNode(int val){
this.val=val;
this.next=null;
}
}
public static int meetingNodeoflength(ListNode head){
if(head==null||head.next==null)
return 0;
ListNode fast=head,slow=head;
int length=0;
while(fast!=null&&fast.next!=null){
length++;
slow=slow.next;
fast=fast.next.next;
//if(slow!=null&&slow==fast)//注意点1:不需要单独讨论slow,因为fast在slow前面,讨论fast是否为空即可
if(slow==fast) break;
}
if(fast==null||fast.next==null) return 0;//注意点2:没有环,则直接返回null。
return length;
}
public static void main(String[] args){
ListNode head = new ListNode(1);
head.next= new ListNode(2);
head.next.next = new ListNode(3);
head.next.next.next = new ListNode(4);
head.next.next.next.next = new ListNode(5);
head.next.next.next.next.next = new ListNode(6);
head.next.next.next.next.next.next =head.next.next ;
System.out.println(meetingNodeoflength(head));
}
}
思路二:
对问题进行分步解决。
(1) 确定链表中是否包含环:双指针,一个每次移动一步,一个每次移动两步,如果两个指针最后相遇,那么就包含环。(注意,移动两步的指针要判断判断其第一步不为空,才能移动第二步)
(2) 确定环中点节点数目:在上面相遇的节点的基础上,移动一个指针,并计数,当指针回到该节点时,确定环中节点数目。
(3) 找到环的入口节点:从头开始,使用两个指针,第一个指针先移动n步(其中n为确定的环中的节点数目),第二个指针再开始同时移动,两个指针相遇的节点即为入口节点。
基于上述思路,C++参考代码如下:
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};
*/
class Solution
{
public:
ListNode* EntryNodeOfLoop(ListNode* pHead)
{
ListNode* meetingNode = MeetingNode(pHead);
if(meetingNode == nullptr)
return nullptr;
//得到环中节点数目
int nodesNumInLoop = 1;
ListNode* pNode1 = meetingNode;
while(pNode1->next != meetingNode)
{
pNode1 = pNode1->next;
++nodesNumInLoop;
}
//先移动pNode1,次数为环中节点数目
pNode1 = pHead;
for(int i=0; i<nodesNumInLoop; ++i)
pNode1 = pNode1->next;
//再移动pNode1和pNode2
ListNode* pNode2 = pHead;
while(pNode1 != pNode2)
{
pNode1 = pNode1->next;
pNode2 = pNode2->next;
}
return pNode1;
}
ListNode* MeetingNode(ListNode* pHead)
{
if(pHead == nullptr)
return nullptr;
ListNode* pSlow = pHead->next;
if(pSlow == nullptr)
return nullptr;
ListNode* pFast = pSlow->next;
while(pSlow != nullptr && pFast != nullptr)
{
if(pSlow == pFast)
return pFast;
pSlow = pSlow->next;
pFast = pFast->next;
if(pFast != nullptr)
pFast = pFast->next;
}
return nullptr;
}
};
测试用例:
a.功能测试(链表中包含或者不包含环;链表中有多个或者只有一个节点)。
b.特殊输入测试(链表头节点为nullptr指针)。
参考:
https://blog.csdn.net/Koala_Tree/article/details/79020771
http://www.cnblogs.com/grandyang/p/4137302.html
http://www.cnblogs.com/hiddenfox/p/3408931.html