142. 环形链表 II

142 阅读2分钟

题目描述

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null

分析

输入:链表头节点 head

输出
若有环,返回环的入口节点 cycleEntry;
若无环,返回 null

解题方法

快慢指针(是否有环[juejin.cn/post/703983…]) + 数学分析

首先先用快慢指针判断是否有环 若有环,判断结束时,slow & fast 均指向环中的同一节点

image.png
(图片来源 leetcode)

设从 head 到环入口节点 cycleEntry 的距离是 a, 相遇时漫指针在环内走过 b, 此时块指针已经走过了 n 圈。

接下来,需要用数学分析两件事:

  1. 快慢指针一定会在漫指针绕环的第一圈相遇

如果慢指针进入环内时,快指针在环内走过了 x,那么设环的长度是 n,两个指针的距离是 n - x
因为两指针速度差距是 1,所以当他们相遇的时候,慢指针走的距离实际上是 n - x,小于环长度 n,所以二者会在第一圈相遇

  1. a === c, 即 headcycleEntry 的距离,是从两指针相遇处到 cycleEntry 的距离

相遇时: slow 走过的距离: d1 = a + b; fast 为 d2 = a + n(b + c) + b

因为速度差一倍,那距离也就差一倍:

所以:2(a + b) = a + n(b + c) + b => a = c + (n - 1)(b + c),可见两者距离一样

代码

/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */

/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var detectCycle = function (head) {
  // 边界: 没有节点,只有一个节点不存在环
  if (!head) return null;
  if (!head.next) return null;

  // 设置变量: 快慢指针
  let slow = head,
    fast = head;

  // 过程:判断是否成环,并使得快慢指针均指向相遇的节点
  while (fast && fast.next) {
    fast = fast.next.next;
    slow = slow.next;

    if (fast === slow) {
      let tmp = head;
      // head,相遇节点一起以相同速度移动,他们相遇的节点就是环入口
      while (tmp !== slow) {
        tmp = tmp.next;
        slow = slow.next;
      }

      return slow;
    }
  }

  return null;
};

复杂度

时间:O(N),包括检测环,找到环入口两个过程,慢指针走过的距离均不过超过 N 空间: O(1),存储快慢指针的变量以及存储 head 用于寻找环入口的变量