Decade
Decade
Published on 2025-07-10 / 4 Visits
0
0

代码随想录11

142. 环形链表 II

已解答

中等

相关标签

premium lock icon相关企业

给定一个链表的头节点  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 或者链表中的一个有效索引

 

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

为什么两个指针从相遇点和起始点出发,最终会在环的开头相遇?

关键推导回顾

设:

  • X = 链表头节点到环入口的距离

  • Y = 环入口到快慢指针相遇点的距离

  • Z = 相遇点沿指针方向回到环入口的距离

  • 环的长度 L = Y + Z

当快慢指针相遇时:

  • 慢指针步数S = X + Y

  • 快指针步数2S = X + Y + n × Ln 是快指针绕环的圈数,n ≥ 1

联立方程:

math

2(X + Y) = X + Y + nL

math

X + Y = nL

math

X = nL - Y

L = Y + Z 代入:

math

X = n(Y + Z) - Y = (n-1)L + Z

物理意义

  • X(头到入口) = Z(相遇点到入口) + (n-1)L(整数倍环长)

  • 这意味着
    从链表头走到环入口的距离 X,等于从相遇点走到环入口的距离 Z 加上 (n-1) 圈环的长度。


双指针移动过程分析

  1. 指针设置

    • 指针A:从链表头出发

    • 指针B:从相遇点出发

  2. 移动规则
    两指针每次都只走 1 步,速度相同。

  3. 相遇时刻

    • 当指针A走了 X 步到达环入口时:

      • 指针B走了 X

      • X = (n-1)L + Z 代入:

        • 指针B的总移动距离 = (n-1)L + Z

        • 这相当于:先走 Z 步到达环入口,再绕环走 (n-1) 圈(每圈 L 步)

    • 最终位置:

      • 指针A:停在环入口

      • 指针B:走完 Z 步到达环入口后,又绕了 (n-1) 圈,最终也停在环入口

    两指针在环入口相遇!


示例演示(n=1 最简单情况)

链表结构
1 → 2 → 3 → 4 → 5 → 3(环入口是节点3,相遇点设为节点5)

  • 计算

    • X = 头到入口 = 2步(1→2→3)

    • Y = 入口到相遇点 = 2步(3→4→5)

    • Z = 相遇点到入口 = 2步(5→6→3,环长 L=4)

    • n = 1(快指针绕1圈)

  • 双指针移动

    步数

    指针A(从头)

    指针B(从相遇点5)

    0

    1

    5

    1

    2

    6

    2

    3

    3(相遇!)

结果:在第2步时,两指针在环入口3处相遇。


示例演示(n=2 复杂情况)

链表结构
1 → 2 → 3 → 4 → 5 → 6 → 3(环入口3,环长 L=4,相遇点设为节点6)

  • 计算

    • X = 头到入口 = 2步(1→2→3)

    • Y = 入口到相遇点 = 3步(3→4→5→6)

    • Z = 相遇点到入口 = 1步(6→3)

    • n = 2(快指针绕2圈)

    • X = (2-1)×4 + 1 = 5

  • 双指针移动

    步数

    指针A(从头)

    指针B(从相遇点6)

    0

    1

    6

    1

    2

    3(走完 Z=1 步到入口3)

    2

    3

    4(绕圈:3→4)

    3

    4

    5(绕圈:4→5)

    4

    5

    6(绕圈:5→6)

    5

    3

    3(绕圈:6→3,相遇!)

结果:在第5步时,两指针在环入口3处相遇。

  • 指针B:先走 Z=1 步到入口,再绕1圈(4步)后回到入口

  • 指针A:走 X=5 步到达入口


核心结论

  • 必然在环入口相遇
    无论 n 是多少,当指针A走完 X 步到达入口时,指针B一定恰好走完 Z + (n-1)L 步,相当于从相遇点走到入口后再绕 (n-1) 圈,最终也停在入口。

  • 数学保证
    公式 X = (n-1)L + Z 决定了两个指针的路径终点必然重合于环入口。

  • 时间复杂度
    最多遍历链表长度 2 遍(找相遇点 + 双指针移动),仍是 O(n)。

这就是 Floyd 环检测算法的精妙之处! 🚀

  1. 最坏情况

    • 当慢指针刚进入环时,快指针正好在慢指针的前一个位置(即距离慢指针 L−1L−1 步)。

    • 此时,快指针需要追赶 L−1L−1 步才能与慢指针相遇。

  2. 慢指针的移动

    • 在这 L−1L−1 步中,慢指针自己也会走 L−1L−1 步。

    • 但由于环的长度是 LL,所以慢指针最多走 L−1L−1不会走完一整圈(即不会回到环的起始点)。

  3. 数学关系

    • 快指针走的步数 = 2×(L−1)2×(L−1)

    • 慢指针走的步数 = L−1L−1

    • 由于快指针速度是慢指针的 2 倍,两者最终会在慢指针走 L−1L−1 步时相遇。


举例验证(环长 L=4L=4

  • 初始状态

    • 慢指针刚进入环(位置 0)。

    • 快指针在位置 3(距离慢指针 L−1=3L−1=3 步)。

  • 移动过程

    步数 tt

    慢指针位置

    快指针位置

    说明

    t=0t=0

    0

    3

    初始状态

    t=1t=1

    1

    (3+2)mod  4=1(3+2)mod4=1

    快指针从 3 → 0 → 1(绕环)

    t=2t=2

    2

    (1+2)mod  4=3(1+2)mod4=3

    快指针从 1 → 3

    t=3t=3

    3

    (3+2)mod  4=1(3+2)mod4=1

    相遇在位置 1

  • 结果

    • 慢指针走了 3 步(L−1=3L−1=3),未走完一圈(4 步)。

    • 快指针走了 6 步(2×32×3),绕环 1 圈半。


为什么这是最坏情况?

  • 如果快指针离慢指针更近(比如距离 2 步),相遇会更快(只需 2 步)。

  • 只有距离 L−1L−1 时,需要最多的 L−1L−1 步才能相遇,因此这是最坏情况。


对算法的影响

  • 由于慢指针最多走 L−1L−1,因此:

    • 公式 S=X+YS=X+Y 中,Y<LY<L(慢指针在环内走的步数 YY 不会超过 L−1L−1)。

    • 这保证了快慢指针方法的时间复杂度是 O(n)O(n),且能正确找到环入口。


总结

  • 你的理解完全正确:在最坏情况下(快指针距离慢指针 L−1L−1 步),慢指针需要走 L−1L−1 步才会被快指针追上,且不会走完一整圈。

  • 这解释了为什么 S=X+YS=X+Y 无需考虑慢指针多走圈数,因为 Y<LY<L

class Solution {
public:
    ListNode* detectCycle(ListNode* head) {
		//对是否存在环进行判断

		ListNode* fast = head;
		ListNode* slow = head;

		//获取关键节点
		ListNode* strat = head;
		ListNode* meetNode = nullptr;


		while (fast != nullptr && fast->next != nullptr) {
			fast = fast->next->next;
			slow = slow->next;
			if (fast == slow) {
				meetNode = fast;
				break;
			}
		}

		if (meetNode == nullptr) {
			return nullptr;
		}

		//如果存在环 则从起点和meetNode同时开始走
		while (strat != meetNode) {

			strat = strat->next;
			meetNode = meetNode->next;

		}
		//此时相遇的节点就是环的起点
		return strat;

    }
};


Comment