[特殊字符] 第28课:相交链表

想系统提升编程能力、查看更完整的学习路线,欢迎访问 AI Compass:https://github.com/tingaicompass/AI-Compass

仓库持续更新刷题题解、Python 基础和 AI 实战内容,适合想高效进阶的你。

📖 第28课:相交链表

模块 :链表 | 难度 :Easy ⭐⭐
LeetCode 链接 :https://leetcode.cn/problems/intersection-of-two-linked-lists/
前置知识 :第24课(反转链表) - 双指针思想
预计学习时间:20分钟


🎯 题目描述

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

注意:相交的定义是两个链表从某个节点开始,后续所有节点都是同一个节点(引用相同),不仅仅是值相同。

示例 1:

复制代码
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,6,1,8,4,5], skipA = 2, skipB = 3
输出:节点8(引用)
解释:
  listA: 4 -> 1 ↘
                 8 -> 4 -> 5 -> null
  listB: 5 -> 6 -> 1 ↗

两个链表在节点8相交

示例 2:

复制代码
输入:intersectVal = 0, listA = [1,2,3], listB = [4,5], skipA = 3, skipB = 2
输出:null
解释:两个链表不相交

约束条件:

  • listA 中节点数目为 m
  • listB 中节点数目为 n
  • 1 <= m, n <= 3 * 10⁴
  • -10⁵ <= Node.val <= 10⁵

进阶 :你能否设计一个时间复杂度 O(m + n)、空间复杂度 O(1) 的解决方案?


🧪 边界用例(面试必考)

用例类型 输入 期望输出 考察点
不相交 [1,2], [3,4] null 基本判断
完全相同 [1,2], [1,2] 节点1 从头相交
等长相交 [1,8,9], [2,8,9] 节点8 长度相同
不等长相交 [4,1,8,9], [5,6,1,8,9] 节点8 长度不同
单节点相交 [1], [2,1] 节点1 极端情况
最大规模 m=30000, n=30000 --- 性能边界

💡 思路引导

生活化比喻

想象两条不同的小路,可能在某个路口汇合,汇合后就变成同一条路。现在你要找到这个汇合点。

🐌 笨办法:你在第一条路上每走一步,就用粉笔在地上做个记号。然后沿着第二条路走,看看哪里有粉笔记号,第一个有记号的地方就是汇合点。这需要很多粉笔(空间)。

🚀 聪明办法:你派两个人,A从第一条路出发,B从第二条路出发:

  • A走完第一条路后,转到第二条路继续走
  • B走完第二条路后,转到第一条路继续走
  • 神奇的是,如果两条路有汇合点,两个人一定会在汇合点相遇!

数学原理:A走了(路A长度 + 路B长度 - 共同部分),B也走了(路B长度 + 路A长度 - 共同部分),距离相等,所以会在汇合点相遇!

关键洞察

双指针等距法:pA走完A再走B,pB走完B再走A。如果有交点,两者会在交点相遇;如果无交点,两者会同时到达null。


🧠 解题思维链

Step 1:理解题目 → 锁定输入输出

  • 输入 :两个链表的头节点 headAheadB
  • 输出 :相交的起始节点,或 null
  • 关键:相交是指节点引用相同,不是值相同

Step 2:先想笨办法(哈希表)

遍历链表A,把所有节点存入集合。然后遍历链表B,第一个在集合中的节点就是交点。

  • 时间O(m+n),空间O(m)

Step 3:瓶颈分析 → 优化方向

能否优化到O(1)空间?

  • 核心问题:"不用额外空间,如何找到交点?"
  • 优化思路:"双指针等距法!让两个指针走相同的距离"

Step 4:选择武器

  • 方案1:哈希表 - O(m)空间
  • 方案2:双指针等距法 - O(1)空间,面试最优

🔑 解法一:双指针等距法(推荐)

思路

用两个指针 pApB:

  • pAheadA 开始,走完A后转到B继续走
  • pBheadB 开始,走完B后转到A继续走

关键定理:

  • 如果有交点,两者会在交点相遇
  • 如果无交点,两者会同时到达null

数学证明:

复制代码
假设:
- A链表独有部分长度 = a
- B链表独有部分长度 = b
- 相交部分长度 = c

有交点情况:
  pA走: a + c + b步到达交点
  pB走: b + c + a步到达交点
  a+c+b = b+c+a,距离相等,会在交点相遇!

无交点情况(c=0):
  pA走: a + b步到达null
  pB走: b + a步到达null
  距离相等,同时到达null!

图解过程

复制代码
示例: A = [4,1,8,4,5], B = [5,6,1,8,4,5]

链表结构:
  A: 4 -> 1 ↘
             8 -> 4 -> 5 -> null
  B: 5 -> 6 -> 1 ↗

  a=2, b=3, c=3

pA的路径: 4 -> 1 -> 8 -> 4 -> 5 -> null -> 5 -> 6 -> 1 -> 8(相遇)
pB的路径: 5 -> 6 -> 1 -> 8(相遇)

计数:
  pA走了: a + c + b = 2 + 3 + 3 = 8步到8
  pB走了: b + c = 3 + 3 = 6步到8

等等,不对?让我重新计算:
  pA走了: a + c = 2 + 3 = 5步到null,然后走b = 3步,共8步到达8
  pB走了: b + c = 3 + 3 = 6步到8

还是不对...正确的是:
  pA走完A(a+c=5步),转到B(走b=3步),总共8步到达交点8
  pB走完B(b+c=6步),转到A(走a=2步),总共8步到达交点8

Python代码

python 复制代码
class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next


def get_intersection_node(headA: ListNode, headB: ListNode) -> ListNode:
    """
    解法一:双指针等距法
    思路:pA走完A再走B,pB走完B再走A,会在交点相遇
    """
    if not headA or not headB:
        return None

    pA, pB = headA, headB

    # 循环直到相遇(交点)或同时为null(无交点)
    while pA != pB:
        # pA走完A后转到B,走完B后到null
        pA = pA.next if pA else headB
        # pB走完B后转到A,走完A后到null
        pB = pB.next if pB else headA

    return pA  # 相交则返回交点,不相交则返回null


# ✅ 测试辅助函数
def create_intersected_lists(listA_vals, listB_vals, skipA, skipB):
    """创建两个相交的链表"""
    # 创建链表A的独有部分
    if skipA == 0:
        headA = None
        tailA = None
    else:
        headA = ListNode(listA_vals[0])
        curr = headA
        for i in range(1, skipA):
            curr.next = ListNode(listA_vals[i])
            curr = curr.next
        tailA = curr

    # 创建链表B的独有部分
    if skipB == 0:
        headB = None
        tailB = None
    else:
        headB = ListNode(listB_vals[0])
        curr = headB
        for i in range(1, skipB):
            curr.next = ListNode(listB_vals[i])
            curr = curr.next
        tailB = curr

    # 创建相交部分
    if skipA < len(listA_vals):
        intersect = ListNode(listA_vals[skipA])
        curr = intersect
        for i in range(skipA + 1, len(listA_vals)):
            curr.next = ListNode(listA_vals[i])
            curr = curr.next

        # 连接
        if tailA:
            tailA.next = intersect
        else:
            headA = intersect

        if tailB:
            tailB.next = intersect
        else:
            headB = intersect

    return headA, headB


# ✅ 测试
listA, listB = create_intersected_lists([4,1,8,4,5], [5,6,1,8,4,5], 2, 3)
result = get_intersection_node(listA, listB)
print(result.val if result else None)  # 期望输出: 8

复杂度分析

  • 时间复杂度😮(m + n) - 每个指针最多走m+n步
  • 空间复杂度😮(1) - 只用了两个指针

优缺点

  • ✅ 空间O(1),最优解
  • ✅ 代码简洁优雅
  • ✅ 数学推导巧妙
  • ❌ 理解需要一点数学直觉

⚡ 解法二:哈希表(简单直接)

Python代码

python 复制代码
def get_intersection_node_hashset(headA: ListNode, headB: ListNode) -> ListNode:
    """
    解法二:哈希表
    思路:遍历A存入集合,遍历B查找第一个在集合中的节点
    """
    visited = set()
    curr = headA

    # 遍历A,存入集合
    while curr:
        visited.add(curr)
        curr = curr.next

    # 遍历B,查找第一个在集合中的节点
    curr = headB
    while curr:
        if curr in visited:
            return curr
        curr = curr.next

    return None

复杂度分析

  • 时间复杂度😮(m + n)
  • 空间复杂度😮(m) - 集合存储链表A的节点

🚀 解法三:长度差法

思路

  1. 先遍历两个链表,计算长度 lenAlenB
  2. 让长链表的指针先走 |lenA - lenB|
  3. 然后两个指针同时前进,第一个相同的节点就是交点

Python代码

python 复制代码
def get_intersection_node_length(headA: ListNode, headB: ListNode) -> ListNode:
    """
    解法三:长度差法
    思路:长链表先走差值步,然后同步前进
    """
    # 计算长度
    def get_length(head):
        length = 0
        while head:
            length += 1
            head = head.next
        return length

    lenA = get_length(headA)
    lenB = get_length(headB)

    # 让长链表先走差值步
    pA, pB = headA, headB
    if lenA > lenB:
        for _ in range(lenA - lenB):
            pA = pA.next
    else:
        for _ in range(lenB - lenA):
            pB = pB.next

    # 同步前进,找交点
    while pA and pB:
        if pA == pB:
            return pA
        pA = pA.next
        pB = pB.next

    return None

复杂度分析

  • 时间复杂度😮(m + n)
  • 空间复杂度😮(1)

📊 解法对比

维度 解法一:等距法 解法二:哈希表 解法三:长度差
时间复杂度 O(m+n) O(m+n) O(m+n)
空间复杂度 O(1) ⭐ O(m) O(1) ⭐
代码难度 简单 简单 中等
面试推荐 ⭐⭐⭐ ⭐⭐ ⭐⭐
优点 最简洁优雅 思路直接 逻辑清晰

面试建议:首选解法一(等距法),代码最简洁,数学推导优雅,是面试官期待的最优解!


🎤 面试现场

面试官:请找出两个链表的相交节点。

:好的,我的思路是用双指针等距法:

两个指针pA和pB分别从headA和headB出发。pA走完A后转到B,pB走完B后转到A。如果有交点,两者会在交点相遇;如果无交点,两者会同时到达null。

数学原理:设A独有部分长度a,B独有部分长度b,相交部分长度c。pA走a+c+b步,pB走b+c+a步,距离相等,会在交点相遇。

时间O(m+n),空间O(1)。

面试官:很好,请写代码。

:(边写边说)两个指针从两个head开始...while循环直到相等...pA走到末尾后转到headB,pB走到末尾后转到headA...

面试官:为什么无交点时会同时到null?

:无交点时相交部分长度c=0,pA走a+b步到null,pB走b+a步到null,距离相等,同时到达null。

高频追问

追问 应答策略
"如果链表有环呢?" 题目假设无环。如果有环,需要先判环(第26课),处理更复杂
"能否只遍历一次?" 解法一本质是遍历两次(A+B和B+A),但已经是最优。长度差法也需要先遍历计算长度
"如果要求返回交点后的所有节点?" 找到交点后,继续遍历返回列表即可
"两个链表完全相同怎么办?" 会在headA/headB处相遇,正确返回headA

🎓 知识点总结

Python技巧卡片 🐍

python 复制代码
# 技巧1:三元表达式切换链表
pA = pA.next if pA else headB

# 技巧2:while循环条件
while pA != pB:  # 不相等继续走,相等(交点或null)退出
    pass

# 技巧3:集合判断节点引用
if node in visited:  # 判断引用相等,不是值相等
    pass

算法模式卡片 📐

  • 模式名称:双指针等距法
  • 适用条件:两个链表/序列找相交点、同步点
  • 识别关键词:"相交链表"、"找交点"、"汇合点"
  • 模板代码:
python 复制代码
def find_intersection_template(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  # 交点或null

易错点 ⚠️

  1. 判断值相等而非引用相等

    • ❌ 错误:if pA.val == pB.val
    • ✅ 正确:if pA == pB(判断节点引用)
  2. 转换链表的条件写错

    • ❌ 错误:pA = headB if not pA else pA.next(顺序反了)
    • ✅ 正确:pA = pA.next if pA else headB
  3. 无交点时死循环

    • ❌ 错误:while pA and pB:(无交点时死循环)
    • ✅ 正确:while pA != pB:(无交点时同时到null退出)

🏋️ 举一反三

题目 难度 相关知识点 提示
LeetCode 141. 环形链表 Easy 快慢指针 第26课,判断链表是否有环
LeetCode 142. 环形链表II Medium Floyd判环 第27课,找环入口,数学推导
LeetCode 19. 删除倒数第N个节点 Medium 快慢指针 快指针先走N步,然后同步前进
LeetCode 876. 链表的中间节点 Easy 快慢指针 fast走2步,slow走1步,fast到末尾时slow在中间

📝 课后小测

题目:给定链表头节点和整数n,删除倒数第n个节点。(LeetCode 19)
💡 提示

用快慢指针:

  1. fast先走n步
  2. fast和slow同时走,fast到末尾时slow在倒数第n+1个节点
  3. 删除slow.next

注意:用虚拟头节点处理删除head的情况
✅ 参考答案

python 复制代码
def remove_nth_from_end(head: ListNode, n: int) -> ListNode:
    dummy = ListNode(0, head)
    fast = slow = dummy

    # fast先走n+1步
    for _ in range(n + 1):
        fast = fast.next

    # 同步前进
    while fast:
        fast = fast.next
        slow = slow.next

    # 删除slow.next
    slow.next = slow.next.next

    return dummy.next

如果这篇内容对你有帮助,推荐收藏 AI Compass:https://github.com/tingaicompass/AI-Compass

更多系统化题解、编程基础和 AI 学习资料都在这里,后续复习和拓展会更省时间。

相关推荐
计算机安禾2 小时前
【数据结构与算法】第32篇:交换排序(一):冒泡排序
c语言·数据结构·c++·算法·链表·排序算法·visual studio code
lxh01132 小时前
蜗牛排序题解
javascript·算法
airuike1232 小时前
高性能MEMS IMU:重构无人机飞行控制核心
人工智能·算法·重构·无人机
win水2 小时前
二十三,哈希表
数据结构·哈希算法·散列表
娇娇爱吃蕉蕉.2 小时前
类和对象的默认成员函数
c语言·开发语言·c++·算法
人道领域2 小时前
【LeetCode刷题日记】哈希表:从0基础到实战全解析
算法·leetcode·哈希算法
py有趣2 小时前
力扣热门100题之矩阵置零
算法·leetcode·矩阵
蚂蚁在飞-2 小时前
Go 1.26
算法
汀、人工智能11 小时前
[特殊字符] 第21课:最长有效括号
数据结构·算法·数据库架构·图论·bfs·最长有效括号