142. 环形链表 II

222 阅读3分钟

题目介绍

力扣142题:leetcode-cn.com/problems/li…

image.png

image.png

方法一:哈希表

一个非常直观的思路是:我们遍历链表中的每个节点,并将它记录下来;一旦遇到了此前遍历过的节点,就可以判定链表中存在环。借助哈希表可以很方便地实现。

代码如下:

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode detectCycle(ListNode head) {
        if(head == null || head.next == null) {
            return null;
        }

        Map<ListNode,Integer> dataMap = new HashMap<>();
        ListNode p = head;
        for(int i = 0; p != null; p = p.next) {
            //如果dataMap中已经存在了该节点,则说明这个节点就是环入口的节点,因为是依次遍历的
            if(dataMap.containsKey(p)) {
                return p;
            }else {
                //否则将该节点存入到dataMap中
                dataMap.put(p,i);
            }
        }
        return null;
    }
}

方法二:快慢指针

我们使用两个指针, fastslow。它们起始都位于链表的头部。随后, slow 指针每次向后移动一个位置,而 fast 指针向后移动两个位置。如果链表中存在环,则 fast 指针最终将再次与 slow 指针在环中相遇。

如下图可以看到,当快慢指针相遇时,让其中任一个指针指向头节点,然后让它俩以相同速度前进,再次相遇时所在的节点位置就是环开始的位置。这是为什么呢?

第一次相遇时,假设慢指针slow走了k步,那么快指针fast一定走了2k步:

image.png

fast一定比slow多走了k步,这多走的k步其实就是fast指针在环里转圈圈,所以k的值就是环长度的「整数倍」

说句题外话,之前还有读者争论为什么是环长度整数倍,我举个简单的例子你就明白了,我们想一想极端情况,假设环长度就是 1,如下图:

image.png

那么fast肯定早早就进环里转圈圈了,而且肯定会转好多圈,这不就是环长度的整数倍嘛。

言归正传,设相遇点距环的起点的距离为m,那么环的起点距头结点head的距离为k - m,也就是说如果从head前进k - m步就能到达环起点。

巧的是,如果从相遇点继续前进k - m步,也恰好到达环起点。你甭管fast在环里到底转了几圈,反正走k步可以到相遇点,那走k - m步一定就是走到环起点了:

image.png

代码如下:

public class Solution {
    public ListNode detectCycle(ListNode head) {
        if (head == null) {
            return null;
        }
        ListNode fast, slow;
        fast = slow = head;
        while (fast != null && fast.next != null) {
            //快指针每次走两步
            fast = fast.next.next;
            //慢指针每次走一步
            slow = slow.next;
            //相遇点,相遇时退出while循环
            if (fast == slow) {
                break;
            }
        }
        
        //从头节点出发以及从相遇点出发,两者肯定会在环入口点相遇
        slow = head;
        while (slow != fast) {
            fast = fast.next;
            slow = slow.next;
        }
        return slow;
    }
}

复杂度分析

  • 时间复杂度:O(N),其中 N 为链表中节点的数目。在最初判断快慢指针是否相遇时,slow 指针走过的距离不会超过链表的总长度;随后寻找入环点时,走过的距离也不会超过链表的总长度。因此,总的执行时间为 O(N)+O(N)=O(N).

  • 空间复杂度:O(1)。我们只使用了 slow,fast两个指针。