题目描述
给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。
解题思路:
两种方法,HashSet结构和双指针做法。
HashSet方法:直接遍历链表,每次指向当前节点的下一个节点。如果当前节点不在set集合中,执行进集合操作。如果当前节点在set集合中,那么直接返回当前节点即为所求的环的入口节点。控制边界,如果链表为空,直接返回为null。代码如下:
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}
*/
import java.util.HashSet ;
public class Solution {
public ListNode EntryNodeOfLoop(ListNode pHead)
{
if(pHead == null) return pHead ;
HashSet<ListNode> ans = new HashSet<>() ;
ListNode temp = pHead ;
while(temp != null) {
if(!ans.contains(temp)){
ans.add(temp) ;
} else
return temp ;
temp = temp.next ;
}
return null ;
}
}
双指针做法:之前做过一道两条链表的公共节点的题目,两个链表有共同的节点,但是两个链表的长度不一定相同。假设长度为a和b。那么想一下a+b = b+a。所以我们可以在第一个链表前面添加第二个链表,在第二个链表前面添加第一个链表进行遍历,如过各自遍历到链表结束,在令其等于自身的链表头节点继续比较,此时两个链表的长度是一样的,这样通过比较就可以得到公共节点。
那么对于这道题来说,可以指定两个指针,一个步幅为1,另一个步幅为2。注意,既然有步幅为2的指针,就需要在循环条件中设定当前节点和下一节点不为null,才可以继续循环,即(head != null && head.next != null) 。因为步幅大的指针,如果没有环的情况下,肯定是在前面的,所以只对它约束就可以。
如果链表中没有环的存在,那么后者先到达链表的尾部,循环结束,在循环中添加break判断,当两个指针相遇的时候,证明链表中存在环,直接跳出循环即可。判断当前循环跳出的条件是否满足存在环的情况,如果当前节点或者下一节点为空,证明无环,return null。否则,令后者返回到链表头节点,进行后面的操作。
这里解释一下,为什么等到两个指针第一次相遇,令后者返回头节点。
如下图所示,假设相遇节点为C,那么慢的指针走了X+Y的长度,那么快的指针走了2(X+Y)的长度相遇。思考一下慢的指针走过的是ABC,后者走过的是ABCDBC。那么2(X+Y) = X + Y + CB + Y。得出CB = X。因此当两个指针相遇,慢的指针在C点处,此时我们令快的指针指回头节点,与慢的指针按照步幅为1同时向后遍历。因为AB = CB = X。所以当两个指针再次相遇的时候,即为B点,也就是所求的环的入口节点。(图为官方提供)
代码如下:
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}
*/
import java.util.HashSet ;
public class Solution {
public ListNode EntryNodeOfLoop(ListNode pHead)
{
if(pHead == null) return pHead ;
ListNode last = pHead,first = pHead ;
while(first!=null && first.next!=null) {
last = last.next ;
first = first.next.next ;
if(last == first) break;
}
if(first == null || first.next == null) return null ;
first = pHead ;
while(first != last) {
first = first.next ;
last = last.next ;
}
return first;
}
}