题目描述
两条链表可能从某个节点起完全重合(从此是同一批节点),返回第一个公共节点,不相交返回 null。
思路解析
先把 A 的每个节点存进一个集合,再遍历 B、第一个已在集合里的节点就是交点。能做对,但要 O(m+n) 空间存下整条 A。
转折:不用哈希。两个指针各从自己链头出发,谁走到头就跳到对方链头继续。这样 pA 走的总长 = lenA+lenB,pB 走的也是 lenB+lenA,步数完全相等——它们会同时到达交点(或同时到 null),只用 O(1) 空间。
观察 · 尾部 [8,4,5] 是共用的:绿色的 [8, 4, 5] 在 A、B 里是同一批节点(只是画了两次)。pA 从 A 的头 4 出发,pB 从 B 的头 5 出发,每步各走一格。
一起走 · pA→1, pB→6:两个指针各走一步:pA 到 A 的 1,pB 到 B 的 6。当前 pA ≠ pB(不是同一节点),继续。
一起走 · pA→8, pB→1:pA 踏进绿色共用段、停在 8;pB 到 B 的 1。小心:pB 在的 1 和 A 里的 1 值一样,但它们是不同节点——必须按节点身份比,不能按值。
一起走 · pA→4, pB→8:pA 到共用段的 4,pB 这时才踏进绿色共用段、停在 8。两者还错着位(pA 比 pB 靠后)——因为 A 短、pA 更早进尾巴。继续走。
pA 走到 A 尾 · pB→4:pA 走到 A 的末尾 5(A 走完了),pB 到共用段的 4。关键转折:pA 下一步不接 null,而是跳到 B 的头去——走完自己接对方。
pA 跳到 B 头 · pB 走到 B 尾:pA 跳到 B 的头(现在它在 B 行的 5 上),pB 走到 B 的末尾 5。两者仍不相等:一个在 B 头、一个在 B 尾。pB 下一步也走完了 B,该跳去 A 头。
pB 跳到 A 头 · 双双继续:现在角色对称了:pB 跳到 A 的头 4,pA 在 B 上走到 6。两个指针都已经「走完一条、踏上另一条」,从此步数完全对齐。
逼近 · pA→B的1, pB→A的1:再走一步:pA 到 B 的 1,pB 到 A 的 1。又一次「值相同但身份不同」:两个 1 看着一样却不是同一节点,pA ≠ pB,还没到交点,继续。
相遇!pA、pB 同时到达 8:pA 走到 B 的 8、pB 走到 A 的 8——而这两个 8 是同一个节点(绿色共用段的头)!pA 共走了 5+3=8 步,pB 也走了 6+2=8 步,步数相等所以同时到达交点,返回它。
两条不等长的链,单独看对不齐;但让每个指针都走「自己 + 对方」这同样的 m+n 长度,长度差就被自动抹平,必在第一个公共节点相遇。无需量长度、无需哈希。
参考代码(Python)
1pA, pB = headA, headB
2while pA is not pB: # 用身份比, 不用值
3 pA = pA.next if pA else headB # A 走完跳到 B 头
4 pB = pB.next if pB else headA # B 走完跳到 A 头
5return pA # 相交点(或同时为 None)复杂度分析
- 时间复杂度:O(m+n) —— 每个指针各走 m+n 步就相遇
- 空间复杂度:O(1) —— 只用 pA、pB 两个指针,不像哈希要存下整条 A
套路模板
记住这四行骨架:谁走到头就换到对方链头继续。两个指针各走 m+n 步,要么在交点相遇、要么同时为 None。短短四行,无需量长度。
1pA, pB = headA, headB
2while pA is not pB:
3 pA = pA.next if pA else headB # 走到头换对方链头
4 pB = pB.next if pB else headA
5return pA易错点
- 错误写法:用 pA.val == pB.val 判相交 → 正确写法:用 pA is pB 判同一节点(相交是「同一个节点」,不同节点可能值相同(如本题 A、B 各有一个 1),必须比身份不比值)
- 错误写法:走到头接 pA.next(自己的头) → 正确写法:走到头跳到对方的链头 headB(换成自己的头会永远错位、步数对不齐,两指针永不相遇)
以上文字与上方动画完全同一思路:先看动画建立直觉 → 读文字巩固 → 关掉页面自己默写一遍代码,能写出来才算真的掌握。