1. 快慢指针+同速双指针

1.1 分析

  • 先利用快慢指针判断是否有环,如果有,两者会在环中相遇。
    图片说明

  • 再利用同速双指针,分别从相遇点和链表头出发,两者一定在环入口相遇。
    证明:
    快指针的路程:s1 = a + k1(b+c) + b
    慢指针的路程:s2 = a + k2(b+c) + b , 令k2 - k1 = k ,则 k >= 1。
    因为速度是慢指针的2倍, 所以s1 = 2 * s2,
    则,a + k1(b+c) + b =2 * ( a + k2(b+c) + b)
    简化,a + b = k (b + c)
    即 a = (k-1)(b+c) +c
    这说明,从头结点和相遇点出发,两只同速指针一定在环入口相遇。

    1.2 代码

    public class Solution {
    
      public ListNode EntryNodeOfLoop(ListNode pHead)
      {
          if(pHead == null ||pHead.next == null) return null;
          ListNode node1 = pHead;
          ListNode node2 = pHead;
          while(node1!=null && node1.next!=null){
              //先寻找是否有环
              //快慢指针一起出发
              node1 = node1.next.next;
              node2 = node2.next;
              if(node1 == node2){//如有环,快慢指针在环中某个结点相遇
                  //使用同速的双指针,找到环的入口
                  //起点分别是head和环中相遇点
                  node2=pHead;
                  while(node1 != node2){
                      node1= node1.next;
                      node2 = node2.next;
                  }
                  return node1;
              }            
          }
          return null;
      }
    }

    1.3 复杂度

    空间:O(1)
    时间:O(n)

    2.快慢指针+环长+双指针

    2.1 分析

  • 同方法一,先判断是否有环

  • 如果有环,计算环的长度n

  • 双指针,都从头结点出发,一个先走n步,然后一起同速出发,最终会在换入口相遇。

    2.2 代码

    public class Solution {
      public ListNode EntryNodeOfLoop(ListNode pHead)
      {
          if(pHead == null ||pHead.next == null) return null;
          ListNode node1 = pHead;
          ListNode node2 = pHead;
          while(node1!=null && node1.next!=null){
              //先寻找是否有环
              //快慢指针一起出发
              node1 = node1.next.next;
              node2 = node2.next;
              if(node1 == node2){//如有环,快慢指针在环中某个结点相遇
                  //计算环得长度
                  int len = 1;
                  node1 = node1.next;
                  while(node1 != node2){
                      node1 =node1.next;
                      len++;
                  }
                  //双指针找换入口
                  //一个指针先走len步
                  for(node1 = pHead; len > 0; node1 = node1.next, len--){};
                  //双指针同速前进,直到相遇
                  node2 = pHead;
                  while(node1 != node2){
                      node1=node1.next;
                      node2=node2.next;
                  }
                  return node1;
              }
          }
          return null;
      }
    }

    2.3 复杂度

    空间:O(1)
    时间:O(n)

    3. HashSet

    3.1 分析

    利用Set去重的特性,注意,set中的重复需要两个结点的hashcode()和equial()都相等。

    3.2 代码

    import java.util.HashSet;
    public class Solution {
      public ListNode EntryNodeOfLoop(ListNode pHead)
      {
          if(pHead == null ||pHead.next == null) return null;
          ListNode tmp = pHead;
          HashSet<ListNode> set = new HashSet<>();
          while(tmp != null){
              if(!set.add(tmp)){
              return tmp;
              }
              tmp =tmp.next;
          }
          return null;
      }
    }

    3.3 复杂度

    空间:O(n)
    时间: O(n)