力扣刷题#14:LeetCode0206-反转链表
题目
给你单链表的头节点 head,请你反转链表,并返回反转后的链表。
输入:head = [1, 2, 3, 4, 5]
输出:[5, 4, 3, 2, 1]
链表基础
节点结构
struct ListNode {
int val;
ListNode *next;
ListNode() : val(0), next(nullptr) {}
ListNode(int x) : val(x), next(nullptr) {}
ListNode(int x, ListNode *next) : val(x), next(next) {}
};
每个节点有两部分:
- val:存储的数据
- next:指向下一个节点的指针,最后一个指向
nullptr
链表和数组最大的区别:数组是连续内存,链表是分散内存。所以不能 list[2] 直接跳到第三个元素,只能从 head 开始顺着 next 走。
head ──→ [1 | next] ──→ [2 | next] ──→ [3 | next] ──→ nullptr
版本一:迭代法(核心)
思路
用三个指针:
prev:指向已反转部分的头(初始为nullptr)cur:当前要处理的节点(初始为head)tmp:保存下一个节点,防止断链
每一轮只做一件事:把 cur→next 掰向 prev。
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if (!head || !head->next) return head;
ListNode* prev = nullptr;
ListNode* cur = head;
while (cur) {
ListNode* tmp = cur->next; // 1. 保存下一个
cur->next = prev; // 2. 反转指向
prev = cur; // 3. prev 前进
cur = tmp; // 4. cur 前进
}
return prev; // 新头
}
};
手走一遍 [1, 2, 3, 4]
初始:prev=null, cur=①
第1轮:tmp=②, ①→next=null, prev=①, cur=②
null ← ① ② → ③ → ④ → null
第2轮:tmp=③, ②→next=①, prev=②, cur=③
null ← ① ← ② ③ → ④ → null
第3轮:tmp=④, ③→next=②, prev=③, cur=④
null ← ① ← ② ← ③ ④ → null
第4轮:tmp=null, ④→next=③, prev=④, cur=null
null ← ① ← ② ← ③ ← ④
循环结束,return prev=④
踩坑记录:while 条件写错了
❌ 错误写法:
while (cur->next) { // 最后一个节点进不了循环!
...
}
return cur; // 返回了最后一个未被反转的节点
为什么错? 当 cur 走到最后一个节点(4)时,cur→next 是 nullptr,循环直接退出——但最后一个节点还没有被反转指向。
✅ 正确写法:
while (cur) { // 所有节点都会处理
...
}
return prev; // 循环结束时 prev 停在最后一个节点
| 写法 | 处理了几个节点 | 返回值 |
|---|---|---|
while(cur->next) |
前 n-1 个 | 最后一个未处理 ❌ |
while(cur) |
全部 n 个 | prev 是新头 ✅ |
为什么 return prev?
循环结束时:
cur已经移到了nullptr(越界了)prev停在了原来的最后一个节点上——也就是反转后的新头
核心原则
每一轮只掰一个节点的指向,掰完往前走。 不要试图在一轮里同时改两个节点的指向——会搞丢后面的链。
版本二:递归法
我的 AC 代码(含注释)
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if (!head || !head->next) return head;
// 递归写法
ListNode* newhead = reverseList(head->next);
// 新的链表头是反转链表函数返回的头,直到尾节点才停止递归
// 现在尾节点才是 newhead 节点
head->next->next = head;
// 现在的头节点是该层函数结束前的头节点
// 现在节点的下一个节点的指针指向现在的头,也就是反转下一个节点的指向
head->next = nullptr;
// 现在的 head 变成新的尾节点
return newhead;
}
};
一句话理解
先反转后面的,再把 head 接到反转结果的尾巴上。
手走一遍 [1, 2, 3, 4]
递归入栈(从上往下):
| 层 | 调用 | 做什么 |
|---|---|---|
| 1 | reverse(1) |
head→next=2≠null → 递归 reverse(2) |
| 2 | reverse(2) |
head→next=3≠null → 递归 reverse(3) |
| 3 | reverse(3) |
head→next=4≠null → 递归 reverse(4) |
| 4 | reverse(4) |
head→next=null → 出口!return 4 |
回归(从下往上,关键反转操作):
每一层回归时,都执行两个操作:
操作①:head→next→next = head ← 先连线:让后面的节点反过来指向 head
操作②:head→next = nullptr ← 再断线:head 的 next 清空,变成新尾巴
reverse(3) 收到 newHead=4:
执行前:4→null(3→next还指向4)
此时 head=3,head->next=4
操作①:head→next→next = head
即 4 →next = 3
操作②:head→next = nullptr
即 3→next = null
结果:4→3→null
return newHead=4
reverse(2) 收到 newHead=4:
执行前:4→3→null(2→next还指向3→next已断)
此时head=2,head->next=3
操作①:head→next→next = head
即 3→next = 2
操作②:head→next = nullptr
即 2 → next = null
结果:4→3→2→null
return newHead=4
reverse(1) 收到 newHead=4:
执行前:4→3→2→null(1→next还指向2)
此时head=1,head->next=2
操作①:head→next→next = head
即 2→next = 1
操作②:head→next = nullptr
即 1→next = null
结果:4→3→2→1→null ✅
return newHead=4
最难理解的一句:head->next->next = head
把它拆开看:
// head=1, head->next=2
head->next->next = head;
// 等价于:2->next = 1
// 意思:让 head 的下一个节点(2)反过来指向 head(1)
为什么先连线再断线? 因为 head→next 还保留着原始指向下一个节点的值,必须先用它来连上新节点,再 head→next = nullptr。如果顺序反了:
head->next = nullptr; // 先断线
head->next->next = head; // null->next → 崩了!
完整变化图(递归回归三步)
第3层回归后: 4 → 3 → null
第2层回归后: 4 → 3 → 2 → null
第1层回归后: 4 → 3 → 2 → 1 → null ✅
迭代 vs 递归对比
| 对比 | 迭代 | 递归 |
|---|---|---|
| 思路 | 从左到右依次反转 | 从右到左,先反转后面再处理头部 |
| 空间 | O(1) | O(n) 递归栈深度 |
| 面试要求 | 必须会 | 加分项 |
复杂度分析
| 维度 | 值 | 说明 |
|---|---|---|
| 时间复杂度 | O(n) | 每个节点处理一次 |
| 空间复杂度(迭代) | O(1) | 只用了三个指针 |
| 空间复杂度(递归) | O(n) | 递归调用的栈深度 |
本文档由 AI 辅助生成,作者提供问题,思路和代码,AI仅负责文本修饰,综合获得以上内容。

浙公网安备 33010602011771号