痛苦的期末,数据结构有关的算法目前一切都让我联想到期末考试的得分,而不是自己真正算法的感悟,好痛苦,我只是不想挂科,我简简单单的愿望就是通过就行,不奢求高分。
今天其实都没在学数据结构,在复习别的科目,但是打卡不能断,写一个简单的算法,至少坚持下去,最困难的日子再怎么样也会过去的。
1 题目
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
示例 1:

输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
示例 2:

输入:head = [1,2]
输出:[2,1]
示例 3:
输入:head = []
输出:[]
提示:
- 链表中节点的数目范围是
[0, 5000] -5000 <= Node.val <= 5000
**进阶:**链表可以选用迭代或递归方式完成反转。你能否用两种方法解决这道题?
2 代码实现
cpp
/**
* Definition for singly-linked list.
* 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) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if (head == nullptr || head -> next == nullptr)
{
return head ;
}
ListNode * cur = head ;
ListNode * prev = nullptr ;
while(cur != nullptr){
ListNode* temp = cur -> next ;
cur -> next = prev ;
prev = cur ;
cur = temp ;
}
return prev ;
}
};
思考
base case head 小于2个,只有一个或者null,直接返回
如果2个及以上,开始反转的操作
怎么回事,自从看了c以后这些我反而不会写指针了,现在有关指针的内容一团浆糊了。
乱写的代码
cpp
/**
* Definition for singly-linked list.
* 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) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if (head == nullptr || head -> next == nullptr)
{
return head ;
}
ListNode * cur = head ;
ListNode * prev = nullptr ;
while(cur != nullptr && cur -> next != nullptr){
ListNode* temp = cur -> next ;
prev = cur ;
cur -> next = prev ;
prev = temp ;
cur = cur -> next ;
}
return prev ;
}
};
问题分析
你的代码主要问题出在反转逻辑的核心循环里:
cur -> next = prev这行代码错误地将当前节点的 next 指向了自己(因为 prev 刚被赋值为 cur),造成了自环- 循环条件
cur != nullptr && cur -> next != nullptr会导致最后一个节点无法被处理 prev和cur的更新逻辑混乱,最终返回的prev并不是反转后的头节点
正确的代码
cpp
/**
* Definition for singly-linked list.
* 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) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if (head == nullptr || head -> next == nullptr)
{
return head ;
}
ListNode * cur = head ;
ListNode * prev = nullptr ;
while(cur != nullptr){
ListNode* temp = cur -> next ;
cur -> next = prev ;
prev = cur ;
cur = temp ;
}
return prev ;
}
};
3 小结
学习
我特别能理解你现在的感受 ------ 期末季的焦虑、对指针的混乱感、只想先通过考试的简单愿望,这些都太真实了。你不用逼自己一下子吃透所有算法,先把能拿分的基础题稳稳掌握,坚持打卡就已经很棒了。
你现在对反转链表的代码已经有了核心认知,只是指针的赋值顺序还没理清楚,我帮你把这个最核心的点讲透,让你能稳稳拿下这道必考点。
先帮你把「能直接过考试」的代码讲透
你修改后的正确代码我再拆解一遍,用最直白的方式解释每一步,让你不仅能背下来,还能理解为什么要这么写:
cpp
/**
* Definition for singly-linked list.
* 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) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
// 基础情况:空链表或只有一个节点,直接返回(不用反转)
if (head == nullptr || head -> next == nullptr)
{
return head ;
}
ListNode * cur = head ; // cur:当前要处理的节点,初始指向头节点
ListNode * prev = nullptr ;// prev:当前节点的「前一个节点」,初始是空(因为反转后头节点要指向null)
// 循环条件只需要cur不为空:只要还有节点没处理,就继续
while(cur != nullptr){
ListNode* temp = cur -> next ; // 第一步:先保存cur的下一个节点(怕断链)
cur -> next = prev ; // 第二步:反转cur的指向(指向前面的节点)
prev = cur ; // 第三步:prev往前走一步(变成当前的cur)
cur = temp ; // 第四步:cur往前走一步(用之前保存的temp)
}
return prev ; // 循环结束后,cur是空,prev就是新的头节点
}
};
用「人话 + 例子」解释指针移动(以 1→2→3 为例)
咱们一步一步走,把指针的变化列出来,你一看就懂:
- 初始状态:prev=null,cur=1(节点 1),temp=2
- 第一步:cur->next = prev → 节点 1 的 next 指向 null(不再指向 2)
- 第二步:prev=cur → prev 变成 1
- 第三步:cur=temp → cur 变成 2
- 第二轮循环:temp=3 → cur->next=prev(节点 2 指向 1)→ prev=2 → cur=3
- 第三轮循环:temp=null → cur->next=prev(节点 3 指向 2)→ prev=3 → cur=null
- 循环结束,返回 prev(也就是 3),最终链表变成 3→2→1
为什么你之前的代码会错?(核心避坑点)
你之前写的 cur -> next = prev; 前面多了一句 prev = cur;,这就导致:
- prev 先变成了 cur(比如 cur 是 1,prev 也变成 1)
- 然后
cur->next = prev就变成了「节点 1 的 next 指向自己」,形成了自环,链表直接断了 - 记住:一定要先保存 temp,再改 cur 的指向,最后再移动 prev 和 cur,顺序不能乱!
适合期末速记的「口诀」
不用记复杂的逻辑,就记这四步口诀,考试直接套:
- 存 temp(temp = cur->next)
- 改指向(cur->next = prev)
- 移 prev(prev = cur)
- 移 cur(cur = temp)
总结
- 反转链表的核心是「先保存下一个节点,再反转当前指针,最后移动双指针」,顺序绝不能乱;
- 循环条件只需要
cur != nullptr,不用加cur->next != nullptr,否则最后一个节点处理不到; - 期末阶段不用追求「算法感悟」,先把这道题的代码背熟、步骤记牢,能稳稳写出正确代码拿分就够了。
你已经在坚持打卡了,这就比很多人强!最难的日子一定会过去,先把这道基础题吃透,一步一个脚印,过线肯定没问题~
反思
调整指针本质就是在调整地址,自己把这个过程理一理,temp一开始的保存其实也只是防止地址丢失?
核心结论先敲定
- 调整指针 = 调整地址 :链表节点的
next指针本质是「存储另一个节点的内存地址」,修改cur->next = prev,就是把cur节点里存的「下一个节点地址」改成prev节点的地址; - temp 的作用 = 防止地址丢失 :在修改
cur->next之前,必须先保存cur->next原本的地址,否则改完后就再也找不到原本的下一个节点,链表直接「断链」。
用「地址视角」拆解反转链表的每一步(以 1→2→3 为例)
为了方便理解,我们给每个节点编上「虚拟地址」(实际是内存地址,这里简化):
- 节点 1:地址 0x01,val=1,next=0x02(指向节点 2)
- 节点 2:地址 0x02,val=2,next=0x03(指向节点 3)
- 节点 3:地址 0x03,val=3,next=0x00(nullptr)
- 初始:prev=0x00(null),cur=0x01(节点 1)
第一轮循环(处理节点 1)
cpp
ListNode* temp = cur->next; // temp = 0x02(保存节点2的地址)
cur->next = prev; // 节点1的next从0x02改成0x00(调整地址,指向prev)
prev = cur; // prev = 0x01(prev变成节点1的地址)
cur = temp; // cur = 0x02(cur拿到保存的地址,指向节点2)
- 为什么要存 temp?如果不存,执行
cur->next = prev后,节点 1 的 next 就变成 0x00 了,你再也拿不到节点 2 的地址(0x02),后续根本没法处理节点 2 和 3; - 这一步的「地址调整」:节点 1 的连接地址从「指向 2」改成了「指向 null」。
第二轮循环(处理节点 2)
cpp
ListNode* temp = cur->next; // temp = 0x03(保存节点3的地址)
cur->next = prev; // 节点2的next从0x03改成0x01(调整地址,指向节点1)
prev = cur; // prev = 0x02(prev变成节点2的地址)
cur = temp; // cur = 0x03(cur拿到保存的地址,指向节点3)
- 此时节点 2 的连接地址从「指向 3」改成了「指向 1」,链表已经形成 2→1 的片段;
- 依然要存 temp:如果不存,改完节点 2 的 next 后,就丢了节点 3 的地址(0x03),没法处理最后一个节点。
第三轮循环(处理节点 3)
cpp
ListNode* temp = cur->next; // temp = 0x00(保存null的地址)
cur->next = prev; // 节点3的next从0x00改成0x02(调整地址,指向节点2)
prev = cur; // prev = 0x03(prev变成节点3的地址)
cur = temp; // cur = 0x00(cur变成null,循环结束)
- 这一步把节点 3 的连接地址改成「指向 2」,最终链表变成 3→2→1;
- 哪怕 temp 存的是 null,也要存:这是统一的操作逻辑,不用额外判断,避免出错。
一句话总结 temp 的「保命作用」
链表的指针操作里,「修改地址」和「访问地址」是互斥的 ------ 你一旦修改了 cur->next 里的地址,原本的地址就被覆盖了,temp 就是在修改前「备份」这个地址,保证你还能找到下一个要处理的节点,不会让链表「断在手里」。
关键点回顾
- 指针的本质是「内存地址」,调整链表指针就是修改节点中存储的「下一个节点地址」;
- temp 的核心作用是「备份地址」,防止修改
cur->next后丢失原本的下一个节点地址,导致断链; - 反转链表的整个过程,就是「备份地址→修改地址→移动地址」的循环,三步缺一不可。