1. 问题背景与理解
这道题目是LeetCode热题100中的第160题,属于链表类问题的经典题型。题目要求我们找到两个单链表相交的起始节点,如果不存在相交节点则返回null。在实际编程面试中,这类链表操作问题出现的频率非常高,因为它能很好地考察面试者对指针操作的理解和边界条件的处理能力。
链表相交的定义需要注意:不是简单的节点值相同,而是节点引用相同。也就是说,两个链表在某个节点开始完全重合,后续所有节点都共享同一段内存地址。举个例子,假设链表A在节点C1之后与链表B共享C2、C3节点,那么C1就是我们要找的相交起始节点。
2. 解题思路分析
2.1 暴力解法(不推荐)
最直观的想法是双层循环遍历:对于链表A的每个节点,都去遍历链表B看是否有相同节点。这种方法时间复杂度O(mn),空间复杂度O(1),在LeetCode上会超时。
python复制# 伪代码示例
for nodeA in listA:
for nodeB in listB:
if nodeA == nodeB:
return nodeA
return None
2.2 哈希表法(推荐)
我们可以先遍历链表A,将所有节点存入哈希集合,然后遍历链表B时检查节点是否存在于集合中。第一个存在的节点就是相交节点。
时间复杂度:O(m+n)
空间复杂度:O(m)或O(n)
python复制def getIntersectionNode(headA, headB):
nodes = set()
while headA:
nodes.add(headA)
headA = headA.next
while headB:
if headB in nodes:
return headB
headB = headB.next
return None
2.3 双指针法(最优解)
这是最巧妙的解法,只需要O(1)的额外空间。基本思路是让两个指针分别遍历两个链表,当到达末尾时切换到另一个链表头部继续遍历。如果链表相交,两个指针必定会在相交点相遇;如果不相交,最终都会到达null。
python复制def getIntersectionNode(headA, headB):
pA, pB = headA, headB
while pA != pB:
pA = pA.next if pA else headB
pB = pB.next if pB else headA
return pA
3. 关键点解析
3.1 为什么双指针法能工作?
假设链表A独立部分长度为a,链表B独立部分长度为b,公共部分长度为c。指针A走的路径为a→c→b,指针B走的路径为b→c→a。两者走过的总长度都是a+b+c,因此必定会在相交点相遇(如果存在)。
3.2 边界条件处理
- 两个链表都为空:直接返回null
- 一个链表为空:肯定不相交
- 链表不相交:最终两个指针都会指向null退出循环
- 链表相交于头节点:直接返回该节点
4. 复杂度对比
| 方法 | 时间复杂度 | 空间复杂度 | 适用场景 |
|---|---|---|---|
| 暴力法 | O(mn) | O(1) | 不推荐实际使用 |
| 哈希表法 | O(m+n) | O(m)或O(n) | 需要额外空间时使用 |
| 双指针法 | O(m+n) | O(1) | 最优解,推荐使用 |
5. 实际编码注意事项
- 节点比较:Python中直接用==比较节点对象即可,其他语言可能需要比较内存地址
- 循环终止条件:双指针法不需要特别处理,因为不相交时最终都会指向null
- 链表修改:注意不要修改原链表结构,特别是面试时可能需要保持输入不变
- 空指针检查:在移动指针前要先检查当前指针是否为null
6. 扩展思考
6.1 如果链表有环怎么办?
原题假设链表无环,如果有环则需要先判断是否有环,并找到环的入口节点。这种情况下相交判断会更复杂,需要结合快慢指针算法。
6.2 如何求相交节点的值而非引用?
如果题目要求返回相交节点的值而非节点本身,方法基本一致,只需最后返回node.val即可。但需要注意值相同的节点不一定是同一个节点。
6.3 实际应用场景
- 内存管理:检测两个对象引用是否共享同一内存块
- 社交网络:查找两个用户的关系链最早交汇点
- 版本控制:查找两个代码分支的最后共同提交
7. 常见错误与调试技巧
- 死循环:确保指针移动逻辑正确,特别是在双指针法中
- 错误比较:比较节点而非节点值
- 边界遗漏:忘记处理一个或两个链表为空的情况
- 调试建议:
- 打印指针移动路径
- 使用小规模测试用例手动验证
- 检查链表长度差异大的情况
8. 不同语言实现要点
8.1 Java实现
java复制public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode pA = headA, pB = headB;
while (pA != pB) {
pA = (pA != null) ? pA.next : headB;
pB = (pB != null) ? pB.next : headA;
}
return pA;
}
8.2 C++实现
cpp复制ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode *pA = headA, *pB = headB;
while (pA != pB) {
pA = pA ? pA->next : headB;
pB = pB ? pB->next : headA;
}
return pA;
}
9. 性能优化技巧
- 提前终止:在哈希表法中,可以记录链表长度,先移动长链表的指针到与短链表相同位置
- 位运算:某些语言可以用位运算优化哈希表的内存使用
- 并行遍历:对于超长链表可以考虑多线程并行遍历(实际面试中不常见)
10. 类似题目推荐
-
- 环形链表(判断链表是否有环)
-
- 环形链表II(找到环的入口)
-
- 反转链表
-
- 回文链表
-
- 删除链表的倒数第N个节点
链表问题的核心在于掌握指针操作和边界条件处理。这道相交链表问题很好地融合了这些考察点,是准备技术面试的必练题目。建议在理解双指针解法后,尝试自己从头实现一遍,并思考如何扩展到更复杂的链表场景。