环形链表 II

104 阅读2分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第27天,点击查看活动详情

一、题目

leetcode 环形链表 II

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

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

不允许修改 链表。

示例 1:

输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。

示例 2:

输入:head = [1,2], pos = 0
输出:返回索引为 0 的链表节点
解释:链表中有一个环,其尾部连接到第一个节点。

示例 3:*

输入:head = [1], pos = -1
输出:返回 null
解释:链表中没有环。

提示:

链表中节点的数目范围在范围 [0, 104] 内
-105 <= Node.val <= 105
pos 的值为 -1 或者链表中的一个有效索引

二、题解

需要找到链表中可能存在循环链表的一个头结点的链表。

方法一

如果链表中存在一个循环链表,那肯定是链表的最后一个节点指向了之前的节点中的一个,那么我们可以找到链表的最后一个节点last,如果last没有下一个节点,那么链表中就不存在环,如果last存在下一个节点,那last指向的节点就是环的头结点了。但是如果真的存在一个环,那么我们平常的遍历下一个节点的方法就没办法找到最后一个节点,进入到环中只会无限的遍历下去,因此需要一个结束环遍历的条件。如果我们一个一个节点遍历下去,存在环的last节点指向的一个节点,肯定是我们之前遍历过的,所以我们可以记录下遍历过的节点(记录的是节点,不是节点值,因为节点值可能有相同的,节点的内存地址不相同的),那么在遍历过程中就判断当前节点之前是否遍历过,如果没有就记录下这个节点继续遍历,如果发现当前节点之前已经遍历过了,那么该节点就是环链表的头结点了,直接返回这个遍历过的节点即可。

方法二

可以使用fastslow两个指针,初始的都指向链表的头结点,然后每次fast指针往后移动两个节点,slow指针就往后移动一个节点,如果不存在环链表,那么fast指针最后肯定没法往后移动,fast指针的下一个节点或者下下个节点不存在,那就是不存在环链表,直接节点,如果存在环路,因为fast指针每次移动两个节点,最后fast指针肯定会在环链表中追上slow指针,即fast = slow,那么fast指针和slow指针相遇的节点肯定是环中的某个节点,因此我们需要根据这个节点再找到环链表的头结点。假设链表头结点到环链表头结点的路径为a,环链表头结点到fast指针和slow指针相遇的节点路径为b,fast指针和slow指针相遇的节点再到环链表头结点的路径为c。fast指针可能走了n圈,那fast指针走的路径就是a+b+n(b+c),那么slow指针的路径就是a+b,fast指针走过的路径长度总是slow指针走过的路径长度的两倍,那么a+b+n(b+c) = 2(a + b),最终a = (n-1)(b+c)+c,因此同时从fast指针和slow指针相遇的节点和链表的头节点开始,继续一个一节点移动,直到再次相遇的节点就是环链表的头结点。

三、代码

方法一 Java代码

public class Solution {
    public ListNode detectCycle(ListNode head) {
        if (head == null) {
            return null;
        }
        ListNode last = head;
        Set<ListNode> nodeSet = new HashSet<ListNode>();
        while (last != null) {
            if (nodeSet.contains(last)) {
                return last;
            }
            nodeSet.add(last);
            last = last.next;
        }
        return null;
    }
}

时间复杂度:O(n),不管是否存在环链表,都需要遍历链表的每一个节点。

空间复杂度:O(n),需要一个哈希表记录已经遍历的节点。


方法二 Java代码

public class Solution {
    public ListNode detectCycle(ListNode head) {
        if (head == null) {
            return null;
        }
        ListNode fast = head;
        ListNode slow = head;
        while (true) {
            if (fast == null || fast.next == null) {
                return null;
            }
            fast = fast.next.next;
            slow = slow.next;
            if (fast == slow) {
                fast = head;
                while (fast != slow) {
                    fast = fast.next;
                    slow = slow.next;
                }
                return fast;
            }
        }
    }
}

时间复杂度:O(n),需要两次遍历节点。

空间复杂度:O(1),只需参数个空间。