相交链表检测算法详解

一、问题描述

1.1 题目要求

LeetCode 160. 相交链表
给定两个单链表的头节点headAheadB,请找出它们的第一个公共节点。如果两个链表没有交点,返回null

关键约束

  • 公共节点是指内存地址相同的节点(非值相同)
  • 链表长度可能不等(0 ≤ 节点数 ≤ 10^4)
  • 节点值范围:[-100, 100] image.png

1.2 典型场景

链表A:1 → 2 → 3↘5 → 6 → 7↗
链表B:   4 → 5 → 6 → 7

第一个公共节点为5(地址相同)

二、解题思路

2.1 方法对比

方法 时间复杂度 空间复杂度 适用场景
双循环对比 O(n²) O(1) 小规模链表
双指针法 O(n) O(1) 常规场景(推荐)
哈希表法 O(n) O(n) 需要记录节点地址时使用

2.2 核心算法:双指针法

2.2.1 算法原理
  1. 长度对齐:先遍历两个链表获取长度差
  2. 差值步进:长链表指针先移动差值步数
  3. 同步遍历:双指针同步移动,首次相遇点即为交点
2.2.2 关键步骤
1. 计算链表A长度:countA
2. 计算链表B长度:countB
3. 计算长度差:diff = |countA - countB|
4. 长链表指针先移动diff步
5. 双指针同步移动,比较节点地址
2.2.3 正确性证明
  • 设公共部分长度为C,非公共部分长度分别为L1和L2
  • 长链表指针先走L1+C+diff = L2+C步(消除长度差)
  • 此时双指针剩余步数相同,必然在公共起点相遇

三、源代码实现

3.1 双循环对比法(暴力解法)

/*** 暴力解法:双层循环遍历* 时间复杂度O(n²),空间复杂度O(1)*/
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {ListNode *pA = headA;// 遍历链表A的每个节点while (pA != NULL) {ListNode *pB = headB;// 遍历链表B的每个节点while (pB != NULL) {// 比较节点地址(关键判断)if (pA == pB) return pA;pB = pB->next;}pA = pA->next;}return NULL;
}

3.2 双指针法(优化解法)

/*** 优化解法:双指针法* 时间复杂度O(n),空间复杂度O(1)*/
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {// 计算链表长度int lenA = getLength(headA), lenB = getLength(headB);// 初始化指针ListNode *pLong = lenA > lenB ? headA : headB;ListNode *pShort = lenA > lenB ? headB : headA;// 长链表先走差值步int diff = abs(lenA - lenB);while (diff--) pLong = pLong->next;// 同步遍历比较while (pLong != NULL && pShort != NULL) {if (pLong == pShort) return pLong;pLong = pLong->next;pShort = pShort->next;}return NULL;
}// 辅助函数:计算链表长度
int getLength(struct ListNode *head) {int count = 0;while (head != NULL) {count++;head = head->next;}return count;
}

四、算法验证

4.1 测试用例

测试场景 输入示例 预期输出
有公共节点 A:1→2→3, B:4→5, 公共部分5→6 节点5
无公共节点 A:1→2, B:3→4 NULL
头节点相交 A:5→6, B:5→6 节点5
尾节点相交 A:1→2→3, B:4→5→3 节点3
空链表 A:NULL, B:1→2 NULL

4.2 复杂度分析

  • 时间复杂度:O(n)
    计算长度需要O(n),对齐操作需要O(n),同步遍历需要O(n),总体线性复杂度

  • 空间复杂度:O(1)
    仅使用常数个指针变量,无需额外存储空间

五、优化总结

5.1 关键优化点

  1. 长度对齐策略:通过先计算长度差消除链表长度差异的影响
  2. 地址比较机制:直接比较节点指针地址而非值,确保找到真正的公共节点
  3. 双指针遍历:将时间复杂度从O(n²)优化到O(n)

5.2 注意事项

  1. 空链表处理:需先判断headA或headB是否为NULL
  2. 指针移动顺序:必须先移动长链表指针再同步遍历
  3. 循环终止条件:使用pLong && pShort防止空指针访问

5.3 扩展思考

  • 哈希表法:可用哈希集合存储链表A的所有节点,然后遍历链表B检查是否存在哈希表中,时间复杂度O(n),空间复杂度O(n)
  • 标记法(破坏性操作):遍历链表时修改节点值作为标记,但会改变原链表结构

通过双指针法在保持空间复杂度的同时,将时间复杂度优化至线性级别,是解决该问题的最优解法。