LeetCode Hot100刷到第 58 题,也就是138. 随机链表的复制时,我一度怀疑这道题的难度是不是标错了。复制链表这种题,遇到的次数不算少:遍历原链表,每经过一个节点就 new 一个节点,然后把 next 接上,结束。但这题里的节点除了 next,还有一个 random 指针,它可能指向链表里的任意一个节点,也可能指向 null。正是这个多余指针,让“复制”从手感上的简单题变成了真正容易卡壳的中等题。如果你也被 random 指针绕晕过,这篇复盘应该能给你一条清晰的行动路径:先分析难点在哪,再给出哈希表正解和空间 O(1) 的进阶解,最后把刷题和面试里容易踩的坑一并排掉。
1. 直觉失效的一题:多了一个 random,深拷贝就不再是遍历
1.1 普通链表复制和深拷贝之间隔着一堵墙
先说概念。LeetCode 138 要求的是“深拷贝”。所谓深拷贝,指的是不能只是让新变量指向同一个链表,比如下面这种写法根本不叫拷贝:
java复制Node copy = head; // 这只是一个新引用,修改 copy 会直接影响原链表
这种是浅拷贝。真正的深拷贝要求 new 出同样数量的 Node,并且所有字段都指向新链表里对应的节点。对于普通单链表,做到这点很轻松:每次 new Node(cur.val),然后 cur 往前走即可。原因是 next 是有序的,你可以先创建当前节点,再创建下一个节点;当前节点的 next 先置为 null 也不影响,后面可以再补。
但随机链表不行,random 不讲顺序。比如一个节点指向链表最后一个节点,当你遍历到第一个节点时,最后一个节点的拷贝还没产生;如果你在那一刻就把 random 字段占了,后面又怎么补救?这个反直觉的点,正是很多人第一次写这道题卡住的原因:单纯按顺序复制,无法处理“随机指向将来时”的节点。
1.2 本质问题:如何找到“被指向节点的对应拷贝”
如果把 138 看成图,每个节点的出度最多为 2,一条边是 next,一条边是 random,它本质上就是一张“出度 ≤ 2”的有向图。复制问题就是在问:如何在新图中保持这种引用结构。它与克隆图的差异只是固定只有两条边、入口是 head。
一旦抽离成这个层面,解法思路也就统一了:我们需要一种方式,给定原链表中的任意节点,能够在 O(1) 时间内找到这个节点在拷贝链表里的对应节点。哈希表,就是实现这种映射最直接的工具。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 哈希表正解:新旧节点的映射关系就是最直接的答案
2.1 核心思路:第一遍只“生”节点,第二遍再“接”指针
这里建议大家养成一个习惯:看到复杂结构复制,先别急着在同一个循环里把所有事干完。哈希表法的通用步骤是:
- 第一遍遍历原链表,用 HashMap 保存原节点到新节点的映射。
- 第二遍再从头遍历原链表,通过 map 找到每个新节点,再按原链表的关系设置新节点的 next 和 random。
写这道题时,最容易犯的错误是“想一遍遍历搞定”:一边 new 新节点,一边设置 random。如果 random 指向的节点已经创建,算运气好;如果指向还没创建的节点,就无从设置。所以必须分成两步走。这个思路可以类比成生活中先建好一堆空白档案,再统一填写档案之间的关联关系,而不是每建一份档案就急着把所有关系线画完。
2.2 最小例子走查
用一个极端简单但覆盖全面的例子来验证:假设原链表有两个节点 A 和 B,其中 A.val = 1,B.val = 2,并且:
- A.random = B
- B.random = A
第一轮后,A 的拷贝 A' 和 B 的拷贝 B' 都创建完成,map 中记录 A -> A',B -> B'。第二轮处理时:
- A'.next = map.get(B) = B'
- A'.random = map.get(B) = B'
- B'.next = map.get(null) = null
- B'.random = map.get(A) = A'
结果 A' 的 next 和 random 都指向 B',B' 的 random 指向 A',和原链表关系完全一致。这个例子同时覆盖了 random 指向后继、指向前驱、以及 null
