题目描述
给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null
若链表中有环,则链表的尾节点指向环的入口节点。
解法有两种,一种是遍历链表,将链表中的节点放入HashSet中,当HashSet已经包含当前访问的节点时,说明该节点就是环的入口节点,该算法的时间复杂度为O(n),空间复杂度为O(n)。
代码如下:
public ListNode EntryNodeOfLoop(ListNode pHead) {
if(pHead == null) return null;
HashSet<ListNode> hashSet = new HashSet<>();
ListNode tmp = pHead;
while (!hashSet.contains(tmp)) {
hashSet.add(tmp);
tmp = tmp.next;
if(tmp == null) return null;
}
return tmp;
}
那么有没有一种空间复杂度为O(1)的算法呢,我们想到用双指针的方法。
我们有两个指针p1和p2,试想一下如何让p1指向链表环的入口节点。
令链表的长度为len,令环的长度为n,当一个节点遍历到第len或len-n个节点时那么该节点就是链表环的入口节点。
假设此时p1位于链表中第n个节点处,p2位于链表中第0个节点处,此时p1、 p2同时向后遍历,每次走一步,当p1共走了len步,p2走了len-n步时,p1==p2且指向环的入口节点。
那么现在的问题是如何使p1先走n步?
我可以让p1和p2都先位于第0个节点,p1每次走一步,p2每次走两步,当p1==p2时,令p1走了x步,则p2走了2x步,2x = n+x,因此x=n,此时p1走了n步。
public ListNode EntryNodeOfLoop2(ListNode pHead) {
if (pHead == null) return null;
ListNode p1 = pHead, p2 = pHead;
do {
p1 = p1.next;
try {
p2 = p2.next;
p2 = p2.next;
} catch (NullPointerException e) {
return null; //说明无环
}
} while (p1 != p2);
p2 = pHead;
while (p1 != p2) {
p1 = p1.next;
p2 = p2.next;
}
return p1;
}