反转链表 图解题解
反转链表不用新建节点——三个指针 prev/cur/next 协作,每轮把一根箭头掉头,走一遍就反转完毕。
反转链表就像把一串珠子逐颗摘下重新倒着穿,每颗要走四步:①用 next 先记住下一颗(这步必须最先做,一旦动了线就找不到后面了);②把 cur 的线扭向 prev(真正的掉头);③prev 踩到 cur 的位置;④cur 踩到 next 记住的那颗。四步走完,这颗珠子反向穿好,再循环处理下一颗。cur 走到 null,prev 就指着新头;全程只改指针方向,不新建节点,O(1) 空间。
这道题到底在问什么
- 输入
- 1 → 2 → 3 → null
- 输出
- 3 → 2 → 1 → null
最优解:一步一步想明白
- 3可以遍历把值压进数组/栈,再倒着 new 一条新链表——但要 O(n) 额外空间,还白白新建了节点。更优雅的是原地:只改指针指向,O(1) 空间。
- 4prev 跟在后面(初始 null)、cur 是当前节点。每一轮都做四个动作:①用 next 记住下一个(防断链);② 把 cur.next 掉头指向 prev(原来的箭头先断开、再接到 prev);③ prev 前进一步;④ cur 前进一步。cur 走到 null 时,prev 就是新头。下面一帧一个动作慢放。
- 5prev=∅ cur=1cur 指向头节点 1,prev 此刻是 null(空)。准备把箭头一根根掉头。
- 6cur=1 next=2动手前先用 next 抓住 2。一旦把 1 的箭头改了,就再也找不到后面——「先记 next」是不断链的命根子。
- 71.next = ∅执行 cur.next = prev:1 原来指向 2 的箭头先断开(图上变成 ·),改指向 prev=null。还好 next 仍抓着 2,没丢。
- 8prev=1 cur=2 · 1→null 已翻好prev 来到刚处理完的 1、cur 来到 2。第一轮四步走完,1 已经「翻好」(它的箭头朝向 null)。
- 9cur=2 next=3第二轮,同样先用 next 记住 3。
- 102.next = 1cur.next = prev:2 原来指向 3 的箭头断开(右边变 ·),改指向 prev=1——于是 2 → 1(左边箭头变 ←)。next 仍抓着 3。
- 11prev=2 cur=3prev 前进到 2、cur 前进到 3。第二根也翻好了。
- 12cur=3 next=∅第三轮,cur 现在是 3。记 next = cur.next,发现 3 后面是 null——说明 3 是原来的最后一个节点,这会是最后一轮。
- 133.next = 2cur.next = prev:3 改指向 prev=2,3 → 2(右边箭头变 ←)。三根箭头现在全部掉头完毕。
- 14prev=3 cur=∅prev 前进到 3、cur 前进到 null,循环结束。此刻 prev 停在 3——它就是反转后的新头:3 → 2 → 1 → null。
- 17链表操作的通用心法:改指针前,先用临时变量抓住会丢失的那一端。反转、删除、合并都靠它,绝不能让链断在手里。
- 20误:cur.next = prev 已执行假设第二轮忘了先存 next,直接执行 cur.next = prev:cur(2)的箭头当场掉头指回 1(左边变 ←)。此刻 2 原本通往 3 的那根线已经被覆盖了。
- 21cur.next 现在 = prev=1,3 失联现在才想起去 nxt = cur.next 抓后面——可 cur.next 已经被改成 prev=1 了!读到的是 1 不是 3。节点 3(以及它后面的整段)彻底失联,再也回不去(图上 3 灰掉)。这就是「改指针前没先抓住后面」的真实灾难——所以铁律是先存 next,再掉头。
- 25这套三指针是所有「局部反转」题的原子操作:LC92 只反转第 m~n 段(先走到 m,再套同样的翻转),LC25 每 K 个一组翻(翻完把段接回去)。学会这一道,链表反转一整类都通了——更多在 链表专题 继续。
⚠️ 容易写错的地方
✗ 错:顺序写反 / 返回 cur
✓ 对:先 nxt=cur.next;返回 prev
这两点下面的实演和小测会专门带你踩一遍
✗ 错:prev 初始化成 head
✓ 对:prev 初始为 None
第三个独立命门:原来的头反转后要指向 null,prev 起步必须是空
完整代码(Python / C++ / Java)
Python
class Solution:
def reverseList(self, head):
prev, cur = None, head
while cur:
nxt = cur.next # ① 先抓住后面,防断链
cur.next = prev # ② 掉头
prev = cur # ③ prev 前进
cur = nxt # ④ cur 前进
return prev # prev 是新头(不是 cur)C++
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode *prev = nullptr, *cur = head;
while (cur) {
ListNode* nxt = cur->next; // ① 先抓住后面
cur->next = prev; // ② 掉头
prev = cur; // ③ prev 进
cur = nxt; // ④ cur 进
}
return prev;
}
};Java
class Solution {
public ListNode reverseList(ListNode head) {
ListNode prev = null, cur = head;
while (cur != null) {
ListNode nxt = cur.next; // ① 先抓住后面
cur.next = prev; // ② 掉头
prev = cur; // ③ prev 进
cur = nxt; // ④ cur 进
}
return prev;
}
}套路模板 · 三指针遍历改链(背下来)
# 凡是「一边遍历一边改指针」的链表题都套
prev, cur = None, head
while cur:
nxt = cur.next # 先抓住后面,防断链
... # 改 cur.next(掉头/跳过/接走)
prev, cur = cur, nxt # 一起前进一步ListNode *prev = nullptr, *cur = head;
while (cur) {
ListNode* nxt = cur->next; // 先抓住后面
// 改 cur->next(掉头/跳过/接走)
prev = cur; cur = nxt; // 一起前进
}ListNode prev = null, cur = head;
while (cur != null) {
ListNode nxt = cur.next; // 先抓住后面
// 改 cur.next(掉头/跳过/接走)
prev = cur; cur = nxt; // 一起前进
}复杂度
时间复杂度
O(n)
每个节点恰好被访问一次,n 个节点 → O(n)
空间复杂度
O(1)
只用 prev/cur/nxt 三个指针,不随链表变长 → O(1)
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 反转链表 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
能用递归吗?+
能。一路递到尾,回溯时把 head.next.next=head 逐个反过来。但递归栈是 O(n) 空间,不如迭代的 O(1)——下面紧跟一帧给你递归完整代码。
递归为什么是 O(n) 空间?+
递归要一直递到最后一个节点才开始回溯,栈里同时压着 n 层调用帧,所以额外空间随链表长度线性增长,是 O(n) 而非 O(1)。
如果链表带环会怎样?+
本题保证无环。若真带环,while cur 永远遇不到 null,会死循环——所以反转前一般默认链表无环,带环要先用快慢指针处理。
能直接反转双向链表吗?+
可以,而且更省事:双向链表每个节点同时有 prev/next,遍历时把每个节点的 prev 和 next 两个字段互换即可,最后整体头尾对调,不必像单链表这样三指针小心翼翼防断链。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。