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 小时前
C++ 数据结构复习笔记
数据结构·c++·笔记
至乐活着2 小时前
深入解析跳表SkipList:原理、实现与性能优化实战
数据结构·算法·跳表·skiplist·java实现
tntxia2 小时前
C++ 基础教程:从入门到精通
c++
Drone_xjw2 小时前
从 GDB 到 CDB:C/C++ 程序调试的两把“手术刀”
c语言·开发语言·c++
r_oo_ki_e_2 小时前
Java Map 集合学习笔记
java·笔记·学习
Jerry3 小时前
LeetCode 383. 赎金信
算法
ai产品老杨3 小时前
H264 H265视频分析常见问题和排查清单
人工智能·算法·音视频
Jerry3 小时前
LeetCode 454. 四数相加 II
算法
笨鸟先飞的橘猫3 小时前
skynet——sharetable学习
学习·skynet
liuyicenysabel4 小时前
大模型学习笔记 · 第八篇 · 进阶:偏好对齐与多卡训练
人工智能·笔记·学习