---
id: "20210402"
date: "2021/04/02 17:15"
title: "Q142 环形链表 II(Linked List Cycle II)"
tags: ["java", "leetcode"]
categories: 
- "算法"
- "leetcode刷题"
---

### 解析思路

&emsp;&emsp;leetcode 中等难度，题目描述[点击这里](https://leetcode-cn.com/problems/linked-list-cycle-ii/)。

&emsp;&emsp;看到题目描述首先蹦出来的解法就是遍历链表，记录走过的节点，当某个节点第二次走到的时候说明该节点就是环的起点，当节点没有下个节点说明没有环。本方法很简单不做详细说明，用一个HashSet记录访问过的节点即可。

&emsp;&emsp;有没有更好的版本呢？当然是有的。通常使用**快慢指针**的办法来解决这类链表环的问题。思路如下：

1. 定义两个指针slow(慢指针，每次走1个)，fast(快指针，每次走两个),都从head出发。
2. 每进行一次移动fast和slow间隔的节点就会+1,由此可以推断当slow进入环后，一定会和fast相遇
<!-- more -->
3. 假设链表非环的长度为a,环的长度为b,fast走过的距离为f,slow走过的距离为s,当fast在环中循环n次后，fast,slow首次相遇，可得到如下表达式：

    f=2s(f的速度是s的两倍)

    f=s+nb(一定比s走的距离多n圈才会相遇)

    由上面两个得到：s=nb,由于s走的距离肯定包含了a,可以得到s在环中走的距离为nb-a,只需要继续走a的距离就能刚好走到环的起点。
4. 所以再加一个指针c从head开始，每次移动一格，当c和slow相遇时说明到了环的起点


java如下：

```java
package com.fanxb.common;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

/**
 * 环形链表 II
 * 题目地址:https://leetcode-cn.com/problems/linked-list-cycle-ii/
 * 解题思路：
 *   解法1：只要找到是否某个节点访问了两次即可，用set记录已访问过的节点，当访问的节点在set中说明找到了环形链表头，当节点没有下一个节点说明不存在环
 *   更优化的解法2：
 *     使用快慢指针，定义两个指针slow(慢指针，每次走1个)，fast(快指针，每次走两个),都从head出发。
 *     每进行一次移动fast和slow间隔的节点就会+1,由此可以推断当slow进入环后，一定会和fast相遇
 *     假设链表非环的长度为a,环的长度为b,fast走过的距离为f,slow走过的距离为s,当fast在环中循环n次后，fast,slow首次相遇，可得到如下表达式：
 *     f=2s(f的速度是s的两倍)
 *     f=s+nb(一定比s走的距离多n圈才会相遇)
 *     由上面两个得到：s=nb,由于s走的距离肯定包含了a,可以得到s在环中走的距离为nb-a,只需要继续走a的距离就能刚好走到环的起点。
 *     所以再加一个指针c从head开始，每次移动一格，当c和slow相遇时说明到了环的起点
 *
 * @author fanxb
 * Date: 2020/6/9 15:10
 */
public class Q142 {

    public static class ListNode {
        int val;
        ListNode next;

        ListNode(int x) {
            val = x;
            next = null;
        }
    }

    public static ListNode solution(ListNode head) {
        Set<ListNode> set = new HashSet<>();
        while (head != null) {
            if (set.contains(head)) {
                return head;
            } else {
                set.add(head);
                head = head.next;
            }
        }
        return null;
    }

    public static ListNode betterSolution(ListNode head) {
        ListNode slow = head, fast = head;
        do {
            if (fast == null || fast.next == null) {
                return null;
            }
            slow = slow.next;
            fast = fast.next.next;
        } while (slow != fast);
        //此时快慢指针相遇,再加一个指针c
        ListNode c = head;
        while (c != slow) {
            slow = slow.next;
            c = c.next;
        }
        return c;
    }

}

```

本体存在一些变体也是一样的解法，

比如找到开始节点后的第n个节点，可在找到头后再遍历n次

比如找到开始节点前的第n个节点，一样的找到头后，再遍历一遍环得到环长度，再计算出需要遍历的次数，得到距离开始节点前的第n个节点


<span id="blogIdSpan" style="display:none">20210402</span>