问题描述
如果一个链表中包含环,如何找到环的入口节点?
例如:1–>2–>3–>4–>5–>6(–>3),在6后并没有结束,其next指向了中间的3,形成了一个环,所以环的入口节点是节点3。
思路
首先,确定一个链表里是否有环。由前面的题目启发,可以设置两个指针,一个快一个慢,然后当快的遇到慢的说明链表有环,如果慢的指针走到链表结尾还没有遇到快的指针,则不包含环。
第二步,找到环的入口。依然可以用两个指针,如果环中包含N个节点,那么第一个指针先向前移动N步,然后两个指针以相同的速度移动,如果第二个指针移动到环的入口节点时,第一个指针已经围绕环走了一圈,又回到环的入口,两指针相遇。
最后,统计环中节点的数目。第一个指针走到环的入口时,开始计数,当两个指针相遇时,得到的数就是环的节点数.
using System;
namespace 链表中环的入口节点
{
public class ListNode
{
public int val;
public ListNode next;
public ListNode(int x)
{
val = x;
}
}
class Program
{
static void Main(string[] args)
{
ListNode list1 = CreateLoopList(null);
PrintList(list1);
Console.WriteLine("\n上述链表中环入口节点为:"+EntryNodeOfLoop(list1).val);
ListNode list2 = CreateNoLoopList(null);
PrintList(list2);
}
/// <summary>
/// 找到链表环的入口节点
/// </summary>
/// <param name="pHead">链表的头节点</param>
/// <returns>环的入口节点</returns>
public static ListNode EntryNodeOfLoop(ListNode pHead)
{
if (pHead == null)
return null;
ListNode meetingNode = MeetingNode(pHead);
if (meetingNode == null)
return null;
//得到环中节点数目
int nodeInLoop = 1;
//快慢指针相遇的节点位置
ListNode pNode1 = meetingNode;
while (pNode1.next != meetingNode)
{
pNode1 = pNode1.next;
nodeInLoop++;
}
//移动第一个指针,次数为环中节点数目
pNode1 = pHead;
for (int i = 0; i < nodeInLoop; i++)
{
pNode1 = pNode1.next;
}
//同时移动两个指针
ListNode pNode2 = pHead;
while (pNode1 != pNode2)
{
pNode1 = pNode1.next;
pNode2 = pNode2.next;
}
return pNode1;
}
/// <summary>
/// 在链表存在环的情况下找到一快一慢指针相遇的节点
/// </summary>
/// <param name="pHead">链表的头节点</param>
/// <returns>相遇的节点</returns>
private static ListNode MeetingNode(ListNode pHead)
{
if (pHead == null)
return null;
//慢指针,一次只走一步
ListNode firstNode = pHead.next;
if (firstNode == null)
return null;
//快指针,一次走两步
ListNode secondNode = firstNode.next;
while (firstNode != null && secondNode != null)
{
if (firstNode == secondNode)
return secondNode;
firstNode = firstNode.next;
secondNode = secondNode.next;
if (secondNode != null)
secondNode = secondNode.next;
}
return null;
}
/// <summary>
/// 创建一个有环的链表
/// </summary>
/// <param name="pHead">链表的头节点</param>
/// <returns>链表的头节点</returns>
private static ListNode CreateLoopList(ListNode pHead)
{
ListNode list = null;
ListNode loopNode = null; //环入口
for (int i = 1; i < 7; i++)
{
ListNode temp = new ListNode(i) { next = null };
//设置第三个节点为环入口节点
if (i == 3)
loopNode = temp;
if (list == null)
{
pHead = temp;
list = temp;
}
else
{
list.next = temp;
list = temp;
}
}
//让链表尾节点指向环入口节点
list.next = loopNode;
return pHead;
}
/// <summary>
/// 创建一个无环的链表
/// </summary>
/// <param name="pHead">链表的头节点</param>
/// <returns>链表的头节点</returns>
private static ListNode CreateNoLoopList(ListNode pHead)
{
ListNode list = new ListNode(1) { next = null };
if (pHead == null)
pHead = list;
else
pHead.next = list;
for (int i = 2; i < 7; i++)
{
ListNode temp = new ListNode(i) { next = null };
list.next = temp;
list = temp;
}
return pHead;
}
/// <summary>
/// 打印链表
/// </summary>
/// <param name="pHead">链表的头节点</param>
private static void PrintList(ListNode pHead)
{
if (pHead == null)
{
Console.WriteLine("null");
return;
}
ListNode list = pHead;
bool isLoop = false;
ListNode meetingNode = MeetingNode(pHead); //找到链表的相遇节点,如果节点不为空,说明有环,令isLoop为真
if (meetingNode != null)
isLoop = true;
int count = 0;
//如果链表有环,输出二十次,否则,输出完一次链表则结束(测试中链表节点数永远小于10,所以设置20可以查看到循环的环节点)
if (isLoop)
{
if (pHead == null)
return;
Console.Write(pHead.val + "-->");
ListNode pNode1 = pHead.next;
if (pNode1 == null)
return;
ListNode pNode2 = pNode1.next;
while (count < 20)
{
count++;
Console.Write(pNode1.val + "-->");
pNode1 = pNode1.next;
}
}
else
{
while (list != null)
{
Console.Write(list.val + "-->");
list = list.next;
}
}
Console.WriteLine();
}
}
}