力扣刷题#20-0160-相交链表
力扣刷题#20-0160-相交链表
题目
给你两个单链表的头节点 headA 和 headB,找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 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 步后,同时到达 nullptr。nullptr == 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仅负责文本修饰,综合获得以上内容。

浙公网安备 33010602011771号