141. 环形链表

148 阅读3分钟

题目介绍

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

image.png

image.png

方法一:哈希表

最容易想到的方法是遍历所有节点,每次遍历到一个节点时,判断该节点此前是否被访问过。

具体地,我们可以使用哈希表来存储所有已经访问过的节点。每次我们到达一个节点,如果该节点已经存在于哈希表中,则说明该链表是环形链表,否则就将该节点加入哈希表中。重复这一过程,直到我们遍历完整个链表即可。

代码如下:

public class Solution {
    public boolean hasCycle(ListNode head) {
        Set<ListNode> seen = new HashSet<ListNode>();
        while (head != null) {
            if (!seen.add(head)) {
                return true;
            }
            head = head.next;
        }
        return false;
    }
}

复杂度分析

  • 时间复杂度:O(N),其中 N 是链表中的节点数。最坏情况下我们需要遍历每个节点一次。

  • 空间复杂度:O(N),其中 N 是链表中的节点数。主要为哈希表的开销,最坏情况下我们需要将每个节点插入到哈希表中一次。

方法二:快慢指针

可以使用快慢指针法, 分别定义 fastslow指针,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fastslow指针在途中相遇 ,说明这个链表有环。

为什么fast 走两个节点,slow走一个节点,有环的话,一定会在环内相遇呢,而不是永远的错开呢?

首先第一点: fast指针一定先进入环中,如果fast 指针和slow指针相遇的话,一定是在环中相遇,这是毋庸置疑的。

那么来看一下,为什么fast指针和slow指针一定会相遇呢?

可以画一个环,然后让 fast指针在任意一个节点开始追赶slow指针。

会发现最终都是这种情况, 如下图:

image.png

fastslow各自再走一步, fastslow就相遇了

这是因为fast是走两步,slow是走一步,其实相对于slow来说,fast是一个节点一个节点的靠近slow的,所以fast一定可以和slow重合。

动画如下:

1614612774-SdmPsd-e6c9d24ely1go4tquxo12g20fs0b6u0x.gif

代码如下:

public class Solution {
    public boolean hasCycle(ListNode head) {
        ListNode fast = head;
        ListNode slow = head;
        // 空链表、单节点链表一定不会有环
        while (fast != null && fast.next != null) {
                fast = fast.next.next; // 快指针,一次移动两步
                slow = slow.next;      // 慢指针,一次移动一步
                if (fast == slow) {   // 快慢指针相遇,表明有环
                        return true;
                }
        }
        return false; // 正常走到链表末尾,表明没有环
    }
}

复杂度分析

  • 时间复杂度:O(N),其中 N 是链表中的节点数。当链表中不存在环时,快指针将先于慢指针到达链表尾部,链表中每个节点至多被访问两次。当链表中存在环时,每一轮移动后,快慢指针的距离将减小一。而初始距离为环的长度,因此至多移动 N 轮。

  • 空间复杂度:O(1)。我们只使用了两个指针的额外空间。