题目描述
给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。
小白思路-HashMap:
有环链表如下:
遍历链表,通过.next不断获取下一个结点,将结点存储在HashMap中,键为结点,值为出现次数,当在keySet中能查找到此结点,说明在链表中已出现过此结点,即构成了环,将其判断为入口节点,否则就将其加入HashMap中。
时间复杂度:O(nlogn)
空间复杂度:O(n)
代码实现:
public class Solution {
public ListNode EntryNodeOfLoop(ListNode pHead)
{
HashMap<ListNode,Integer> nodes = new HashMap<ListNode,Integer>();
while(pHead != null)
{
for (ListNode node: nodes.keySet())
{
//每次都要遍历HashMap,查询是否键中已添加此结点
while(node == pHead)
{
return pHead;
}
}
//HashMap中没有此结点,则添加
nodes.put(pHead,1);
pHead = pHead.next; //指向下一个结点
}
return null;
}
}
虽然采用了HashMap来存储结点,但是仍添加了额外的存储空间。
大牛思路-双指针法
一、首先判断链表中是否存在环。
-
采用快慢指针来解决这个问题。定义两个指针,同时从链表的头结点出发,一个指针一次走一步,另一个指针一次走两步。
-
如果走得快的指针追上了走得慢的指针,那么链表就包含环;如果走得快的指针走到了链表的末尾(ListNode的next指向null)都没有追上慢指针,那么链表就不包含环。
为何说二者一定相遇?因为fast会先进入环,在slow进入之后,如果把slow看做在前面,则fast在后面每次循环都会想slow靠近1,所以一定会相遇,而不会出现fast直接跳过slow的情况。
二、找到环中节点的数目。
这一步的目的就是为寻找环的入口做铺垫。快慢两个指针相遇的节点一定是在环中。
- 可以从这个节点出发,一边继续向前移动一边计数,当再次回到这个节点时,就可以得到环中节点数了。
- 在下次相遇时,fast比slow正好多走了一圈,即多走的距离正好等于环长。
设从第一次相遇到第二次相遇,设slow走了len步,则fast走了2len步,相遇时多走了一圈:
环长=2len-len。
三、找到环的入口。
- 先定义两个指针P1和P2指向链表的头结点。如果链表中的环有n个节点,则指针P1先在链表上向前移动n步,然后两个指针以相同的速度向前移动。直到两个指针相遇,即环的入口。
- 第一次碰撞点Pos到连接点Join的距离 = 头指针到连接点Join的距离,因此,分别从第一次碰撞点Pos、头指针head开始走,相遇的那个点就是连接点。
时间复杂度:O(n)
空间复杂度:O(1)
代码实现:
public class Solution {
public ListNode meetingNode(ListNode head)
{
//判断链表是否存在环
ListNode fast = head;
ListNode slow = head;
if(head == null)
return null;
while(fast != null)
{
slow = slow.next;
fast = fast.next;
if(fast != null)
fast = fast.next;
if( fast!= null && fast == slow && slow != null)
return fast;
}
return null;
}
public ListNode EntryNodeOfLoop(ListNode pHead)
{
ListNode meetingNode = meetingNode(pHead);
if(meetingNode == null)
return null;
int number = 1;
//number存储环的结点数目
ListNode nextNode = meetingNode.next;
while(meetingNode != nextNode)
{
nextNode = nextNode.next;
number++;
}
//获取环的入口结点
ListNode fast = pHead;
ListNode slow = pHead;
for(int i = number; i > 0; i--)
{
fast = fast.next;
}
while(fast != slow)
{
fast = fast.next;
slow = slow.next;
}
return fast;
}
}
总结:
链表中环的入口结点问题采用双指针的方法,将问题分为三个小问题:
1、链表中是否存在环;
2、环的长度;
3、环的入口结点。
扩展:有环单链表的链表长
由2已知环的长度,由3已知入口结点的位置,就可以求出头结点到连接点的长度。两者相加就是链表的长度。
PS:关于环形链表
环形链表
类似于单链表,也是一种链式存储结构,环形链表由单链表演化过来。单链表的最后一个结点的链域指向NULL,而环形链表的建立,不要专门的头结点,让最后一个结点的链域指向链表结点。 简单点说链表首位相连,组成环状数据结构。如下图结构: