1. 问题背景与核心需求
这道题目是LeetCode热题100中的第18题,编号160。题目要求我们找出两个单链表相交的起始节点。在实际编程面试中,链表相关的问题出现频率极高,而相交链表又是其中的经典题型。它不仅考察对链表结构的理解,更考验解题者的思维灵活性和算法优化能力。
我们先明确几个关键术语:
- 单链表:每个节点包含值和指向下一个节点的指针
- 相交节点:两个链表从某个节点开始,后续节点完全重合
- 时间复杂度:算法执行时间随数据规模的变化关系
- 空间复杂度:算法执行所需额外空间随数据规模的变化关系
2. 解法思路分析与比较
2.1 暴力解法(双重循环)
最直观的解法是使用双重循环遍历两个链表:
python复制def getIntersectionNode(headA, headB):
while headA:
temp = headB
while temp:
if headA == temp:
return headA
temp = temp.next
headA = headA.next
return None
时间复杂度:O(m*n)(m和n分别是两个链表的长度)
空间复杂度:O(1)
提示:虽然这种解法简单直接,但在面试中仅作为最后的选择,因为它效率太低。
2.2 哈希集合法
我们可以利用哈希集合存储其中一个链表的所有节点:
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
时间复杂度:O(m+n)
空间复杂度:O(m)或O(n)
2.3 双指针法(最优解)
最巧妙的解法是使用双指针,让两个指针分别遍历两个链表后交换起点:
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
时间复杂度:O(m+n)
空间复杂度:O(1)
3. 双指针法深度解析
3.1 算法原理
双指针法的精妙之处在于它消除了两个链表的长度差异。当较短的链表先到达末尾时,让它从另一个链表的头部继续遍历;同理,较长的链表在到达末尾后也从短链表的头部继续。这样两个指针最终会同时到达相交节点(如果存在的话),或者在遍历完两个链表后同时到达None(如果不存在相交)。
3.2 数学证明
假设:
- 链表A的非公共部分长度为a
- 链表B的非公共部分长度为b
- 公共部分长度为c
当两个指针分别遍历完自己的链表并交换起点后:
- 指针1走过的路径:a + c + b
- 指针2走过的路径:b + c + a
可以看到两者走过的路径长度完全相同,因此必定会在相交点相遇。
3.3 边界条件处理
需要考虑的特殊情况包括:
- 两个链表都为空
- 其中一个链表为空
- 两个链表不相交
- 链表有环(虽然题目说明是无环链表,但实际面试中可能会被问到)
4. 代码实现细节与优化
4.1 Python实现完整版
python复制class ListNode:
def __init__(self, x):
self.val = x
self.next = None
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
if not headA or not headB:
return None
pA, pB = headA, headB
while pA != pB:
pA = pA.next if pA else headB
pB = pB.next if pB else headA
return pA
4.2 时间复杂度分析
- 最好情况:两个链表长度相同且相交 → O(n)
- 最坏情况:两个链表不相交 → O(m+n)
- 平均情况:O(m+n)
4.3 空间复杂度优化
双指针法已经是最优空间复杂度O(1),无需额外优化。相比之下,哈希集合法虽然时间复杂度相同,但需要O(n)的额外空间。
5. 常见问题与调试技巧
5.1 为什么我的双指针法陷入死循环?
可能原因:
- 链表存在环(与题目假设矛盾)
- 指针移动逻辑错误(如忘记判断pA/pB是否为None)
- 初始条件处理不当(如未考虑空链表情况)
调试方法:
- 打印每次循环时两个指针的位置
- 设置最大循环次数限制
- 使用小规模测试用例验证
5.2 如何验证解法正确性?
建议测试用例:
- 两个不相交的链表
- 一个链表是另一个的子集
- 在中间节点相交
- 在尾节点相交
- 一个空链表和一个非空链表
5.3 实际工程中的注意事项
- 链表节点可能非常大,哈希集合法会消耗大量内存
- 多线程环境下需要考虑链表被修改的情况
- 真实场景中链表可能有环,需要先检测环的存在
6. 算法扩展与变种
6.1 如果链表可能有环怎么办?
这种情况下需要先检测环的存在:
- 使用快慢指针检测环
- 如果存在环,找到环的入口点
- 根据环的位置判断是否相交
6.2 如何找出所有相交节点?
如果需要找出所有相交节点(而不仅仅是第一个),可以:
- 先找到第一个相交节点
- 然后继续遍历公共部分
- 记录所有相同节点
6.3 如果允许修改链表结构
如果允许修改链表节点,可以:
- 遍历第一个链表时反转指针方向
- 遍历第二个链表时检查是否遇到反转的指针
- 最后恢复链表结构
7. 面试技巧与实战建议
7.1 如何向面试官解释思路
建议采用以下步骤:
- 先描述暴力解法及其缺点
- 提出哈希集合法的改进
- 最终引出双指针法的最优解
- 用图示辅助说明双指针法的原理
7.2 白板编程时的注意事项
- 先画出示意图
- 明确标注指针移动路径
- 考虑边界条件
- 写完代码后逐步验证
7.3 时间复杂度分析的表达技巧
不要只说"时间复杂度是O(n)",应该:
- 解释n代表什么(链表长度)
- 说明最坏/最好情况
- 与空间复杂度一起分析
8. 同类题目推荐
为了巩固链表相关的算法能力,建议练习以下题目:
-
- 环形链表(判断链表是否有环)
-
- 环形链表II(找出环的入口)
-
- 反转链表
-
- 合并两个有序链表
-
- 删除链表的倒数第N个节点
链表问题的解决往往需要灵活运用指针技巧,多练习才能掌握其中的规律。这道相交链表题目虽然表面简单,但包含了链表问题中常见的解题思路和优化方法,值得反复思考和练习。