算法题分享 | 环形链表 II

84 阅读3分钟

横屏壁纸|种自己的花 爱自己的宇宙🌸_3_任性研究社_来自小红书网页版.jpg

题目

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

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

不允许修改 链表。

示例 1:

image.png

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

示例 2:

image.png

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

示例 3:

image.png

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

提示:

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

 

进阶: 你是否可以使用 O(1) 空间解决此题?

题解

解题思路

这道题要做出来并不难,最直接的方法是遍历链表时使用一个 Set 存储遍历过的元素,对于每个节点都判断是否已经在 Set 集合中,如果是则该节点为入环点,这种方法比较简单,这里就不再给出代码。该解法的缺点是空间复杂度是O(N),下面着重介绍空间复杂度为 O(1) 的快慢指针解法

我们可以使用两个指针,分别为快指针 fast 和慢指针 slow,最开始两者都指向 head,接下来 slow 每次向后移动一个节点,fast 每次向后移动两个节点,如果链表中有环,那么两者最终肯定会相遇。但是两者是不一定会在入环点相遇的,相遇后又应该如何进一步确定入环点呢?

假设从 head 到入环点的节点数为 a,从入环点到相遇点的节点数为 b,然后从相遇点再往后重新回到入环点的节点数为 c,很容易可以得到,在相遇时,slow 走过的节点数为 a + b,而 fast 走过的节点数为 a + n*(b + c) + b,其中 n 为 fast 在环中走过的圈数。

然后,因为在任意时候,fast 走过的距离都是 slow 的两倍,所以可以得到 2*(a + b) = a + n*(b + c) + b,进一步化简为 a = (n-1)b + nc,进而得到 a = (n-1)*(b+c) + c,从中可以看出, a 的距离等于 n-1 圈环加上 C 的距离,因此从相遇点后,使用指针 p 从 head 出发,而 slow 从相遇点出发,每次移动一个节点,最终两者一定会在入环点相遇,即可得到入环点。

代码

Java 题解

/**
 * 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) {
        // 慢指针
        ListNode slow = head;
        // 快指针
        ListNode fast = head;

        while(fast != null) {
            slow = slow.next;
            fast = fast.next;
            if (fast != null) {
                fast = fast.next;
            }
            if (fast != null && slow == fast) {
                // 快慢指针相遇
                ListNode p = head;
                // 寻找入环点
                while(p != slow) {
                    p = p.next;
                    slow = slow.next;
                }
                return p;
            }
        }

        return null;
    }
}

Go 题解

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
func detectCycle(head *ListNode) *ListNode {
    slow, fast := head, head
    
    for fast != nil {
        slow = slow.Next
        if fast.Next == nil {
            return nil
        }
        fast = fast.Next.Next
        if fast == slow {
            p := head

            for p != slow {
                p = p.Next
                slow = slow.Next
            }
            return p
        }
    }

    return nil
}

复杂度分析

  • 时间复杂度:O(N)
    其中 N 是链表的节点数。
  • 空间复杂度:O(1)
    这种解法没有多余的空间开销,只使用了 slow、fast、p 三个指针。