Leetcode 102 反转链表

痛苦的期末,数据结构有关的算法目前一切都让我联想到期末考试的得分,而不是自己真正算法的感悟,好痛苦,我只是不想挂科,我简简单单的愿望就是通过就行,不奢求高分。

今天其实都没在学数据结构,在复习别的科目,但是打卡不能断,写一个简单的算法,至少坚持下去,最困难的日子再怎么样也会过去的。

1 题目

206. 反转链表

给你单链表的头节点 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 ;
        
    }
};

问题分析

你的代码主要问题出在反转逻辑的核心循环里:

  1. cur -> next = prev 这行代码错误地将当前节点的 next 指向了自己(因为 prev 刚被赋值为 cur),造成了自环
  2. 循环条件 cur != nullptr && cur -> next != nullptr 会导致最后一个节点无法被处理
  3. prevcur 的更新逻辑混乱,最终返回的 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,顺序不能乱!

适合期末速记的「口诀」

不用记复杂的逻辑,就记这四步口诀,考试直接套:

  1. 存 temp(temp = cur->next)
  2. 改指向(cur->next = prev)
  3. 移 prev(prev = cur)
  4. 移 cur(cur = temp)

总结

  1. 反转链表的核心是「先保存下一个节点,再反转当前指针,最后移动双指针」,顺序绝不能乱;
  2. 循环条件只需要 cur != nullptr,不用加 cur->next != nullptr,否则最后一个节点处理不到;
  3. 期末阶段不用追求「算法感悟」,先把这道题的代码背熟、步骤记牢,能稳稳写出正确代码拿分就够了。

你已经在坚持打卡了,这就比很多人强!最难的日子一定会过去,先把这道基础题吃透,一步一个脚印,过线肯定没问题~

反思

调整指针本质就是在调整地址,自己把这个过程理一理,temp一开始的保存其实也只是防止地址丢失?

核心结论先敲定

  1. 调整指针 = 调整地址 :链表节点的 next 指针本质是「存储另一个节点的内存地址」,修改 cur->next = prev,就是把 cur 节点里存的「下一个节点地址」改成 prev 节点的地址;
  2. 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 就是在修改前「备份」这个地址,保证你还能找到下一个要处理的节点,不会让链表「断在手里」。

关键点回顾

  1. 指针的本质是「内存地址」,调整链表指针就是修改节点中存储的「下一个节点地址」;
  2. temp 的核心作用是「备份地址」,防止修改 cur->next 后丢失原本的下一个节点地址,导致断链;
  3. 反转链表的整个过程,就是「备份地址→修改地址→移动地址」的循环,三步缺一不可。
相关推荐
今儿敲了吗2 小时前
01|多项式输出
c++·笔记·算法
程序员Jared2 小时前
C++11—mutex
c++
朔北之忘 Clancy3 小时前
2025 年 9 月青少年软编等考 C 语言一级真题解析
c语言·开发语言·c++·学习·数学·青少年编程·题解
Xの哲學3 小时前
深入剖析Linux文件系统数据结构实现机制
linux·运维·网络·数据结构·算法
量子炒饭大师3 小时前
【C++入门】Cyber底码作用域的隔离协议——【C++命名空间】(using namespace std的原理)
开发语言·c++·dubbo
AlenTech3 小时前
200. 岛屿数量 - 力扣(LeetCode)
算法·leetcode·职场和发展
C雨后彩虹3 小时前
竖直四子棋
java·数据结构·算法·华为·面试
wxr06163 小时前
GIT学习
git·学习
REDcker3 小时前
RTCP 刀尖点跟随技术详解
c++·机器人·操作系统·嵌入式·c·数控·机床