力扣刷题#20-0160-相交链表

力扣刷题#20-0160-相交链表

题目

给你两个单链表的头节点 headAheadB,找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null

输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,6,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at '8'
解释:相交节点的值为 8(从节点 8 开始,后续节点完全共享)
输入:listA = [2,6,4], listB = [1,5]
输出:null
解释:两个链表不相交

注意:"相交"指的是两个指针指向同一个节点对象(同一个内存地址),而不是节点的 val 相等。


我的思路之旅

第一反应:"没什么思路"

看到这题的第一个念头就是——懵了

我知道相交意味着两条链表共享尾部节点,但要怎么在两条长度可能不同的链表里找到交点?暴力遍历 O(n²) 显然不对。


关键洞察:如果两链表一样长就好了

假想一下,如果两条链表长度相同

A: 4 → 1 → 8 → 4 → 5 → null
B: 5 → 6 → 8 → 4 → 5 → null   (假设一样长)

那太简单了:两个指针同步走,第一个相同的节点就是交点。

问题就在于它们长度不一样。


破局思路:"补平"长度差

既然长度不同,那就让两个指针各走一遍对方的路:

pA 走完链表 A → 跳到链表 B 的头继续走
pB 走完链表 B → 跳到链表 A 的头继续走

这样两个指针走过的总路程完全相等lenA + lenB。它们在"重走对方的路"过程中,自然就对齐了。

生活化类比

两条长短不同的绳子首尾相接,组成两个一样长的环。从各自的起点出发,走过相同的路程后,如果有共同部分,它们一定会在共同部分的起点相遇。如果没有共同部分,它们会在终点(nullptr)相遇。


推导演示

有交点的情况

A: a1 → a2 ↘
              c1 → c2 → c3 → null
B: b1 → b2 → b3 ↗

pA 路径: a1→a2→c1→c2→c3→null→b1→b2→b3→c1   ← 在 c1 停下
pB 路径: b1→b2→b3→c1→c2→c3→null→a1→a2→c1   ← 在 c1 停下

两个指针都走过 lenA + lenB 个节点后,同时到达 c1。pA == pB → 返回 c1 ✓

无交点的情况

A: 1→2→3→null
B: 4→5→null

pA 路径: 1→2→3→null→4→5→null
pB 路径: 4→5→null→1→2→3→null

两个指针都走过 2+3=5 步后,同时到达 nullptrnullptr == nullptr,循环退出,返回 nullptr


我的 AC 代码

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode* a = headA, *b = headB;
        while (a != b) {
            a = (a == nullptr) ? headB : a->next;
            b = (b == nullptr) ? headA : b->next;
        }
        return a;
    }
};

核心就在四行。

代码逐行解析

第 4 行:初始化

ListNode* a = headA, *b = headB;

两个指针分别从两条链表的头出发。

第 5 行:循环条件

while (a != b) {

只要两个指针指向的不是同一个节点,就继续走。退出条件只有两种:

情况 a 和 b 的值 含义
有交点 都指向交点 返回交点
无交点 都是 nullptr 返回 nullptr

第 6-7 行:跳转逻辑

a = (a == nullptr) ? headB : a->next;
b = (b == nullptr) ? headA : b->next;
条件 a 操作 b 操作
当前不为空 走下一步 a->next 走下一步 b->next
走到头了 (nullptr) 跳到 B 的头 headB 跳到 A 的头 headA

关键细节:用 a == nullptr 做判断,而不是 a->next == nullptr

  • a == nullptr:指针走穿了再跳 → 正确,多走一个 nullptr 保证总路程对齐
  • a->next == nullptr:指向最后一个节点就跳 → 错误,会跳过 nullptr,总路程不对齐

踩坑记录

坑 1:用 a->next == nullptr 判断

// ❌ 第一版
if (a->next == nullptr) { a = headB; }
a = a->next;   // 又往前走了一步!跳转白跳了

问题:设 a = headB 之后,下一行 a = a->next 把 a 推到了 headB->next。跳转等于是跳过了一个节点。

修复:用三元运算符或 if-else,将"跳转"和"走下一步"放在同一个赋值语句里,互斥执行。

坑 2:自己引入长度变量做冗余判断

int lena=0, lenb=0, la=0, lb=0;
// ...各种计数逻辑...
if (la+lb > lena+lenb) return nullptr;  // 第一轮就触发

这轮复杂逻辑完全不需要。lenA + lenB 这个值不需要手动记录——当两个指针都到达 nullptr 时自然会退出循环,不需要人工设阈值。


关于效率的思考

我当时有个疑问:如果 A 长 97,B 长 89(两个大质数),两个指针走 97+89=186 步,会不会太慢?

答案是不会。 因为:

方案 总步数 复杂度
暴力双循环 O(n²) 几十万步
先算长度再对齐 ≈ lenA + lenB 186 步
跳转法(我的代码) = lenA + lenB 186 步

跳转法和"先算长度"走的步数几乎一样,都是线性 O(n)。质数长度不影响——百万级节点也不过两百万步,CPU 毫秒搞定。


复杂度分析

维度 说明
时间复杂度 O(lenA + lenB) 两个指针各遍历一次自己链表 + 一次对方链表
空间复杂度 O(1) 只用了 a、b 两个指针

关键点总结

关键点 说明
相交定义 指向同一个节点对象,不是值相等
核心技巧 pA 走完跳 B,pB 走完跳 A,总路程对齐
跳转时机 a == nullptr 时跳,不是 a->next == nullptr
循环条件 while(a != b),无交点时两指针同时到 nullptr
四行核心 while + 两行三元 + return,无需额外变量

本文档由 AI 辅助生成,作者提供问题,思路和代码,AI仅负责文本修饰,综合获得以上内容。

posted @ 2026-06-25 21:33  STA_running  阅读(5)  评论(0)    收藏  举报