【LeetCode Hot 100 刷题日记(23/100)】206. 反转链表 —— 链表、递归、迭代、指针操作🔄

8 阅读7分钟

📌 题目链接:206. 反转链表 - 力扣(LeetCode)

🔍 难度:简单 | 🏷️ 标签:链表、递归、迭代、指针操作

⏱️ 目标时间复杂度:O(n)

💾 空间复杂度:O(1)(迭代) / O(n)(递归)

✅ 本题为 后端开发高频面试题,常出现在字节跳动、腾讯、阿里、美团等大厂的算法笔试和面试中。

🔥 考点覆盖:链表结构理解、指针操作、递归思维、空间优化意识

🧠 掌握本题,你将学会如何在有限空间内高效操作链表,并为后续“回文链表”、“K 个一组翻转链表”等难题打下坚实基础!


🧩 题目分析

给你单链表的头节点 head,请你反转链表,并返回反转后的链表。

🎯 示例说明

  • 输入head = [1,2,3,4,5]
  • 输出[5,4,3,2,1]

🔍 关键观察

  • 单向链表只支持从头到尾遍历,无法直接访问前驱节点。
  • 反转操作本质是 修改每个节点的 next 指针方向
  • 必须保存当前节点的下一个节点,否则会丢失链表信息。

❗ 常见误区

  1. 忽略边界情况:空链表或只有一个节点的情况。
  2. 递归时忘记断开原链表连接,导致环形链表。
  3. 使用额外数组存储节点,破坏了“原地操作”的最优解思想。

🔍 核心算法及代码讲解

✅ 方法一:迭代法(推荐)

🎯 核心思想:

通过 三指针技巧 实现原地反转:

  • prev:记录前一个节点(初始为 nullptr)
  • curr:当前处理的节点(初始为 head)
  • next:临时保存下一个节点,防止断链

每一步执行:

next = curr->next;      // 保存下一个节点
curr->next = prev;      // 反转指针方向
prev = curr;            // 移动 prev
curr = next;            // 移动 curr

📌 为什么叫“三指针”?

因为我们在一次循环中同时维护三个关键位置:

  • 当前节点
  • 上一个节点
  • 下一个节点

这确保我们不会丢失任何链表元素。

✅ 优势:

  • 时间复杂度 O(n)
  • 空间复杂度 O(1)
  • 易于理解和实现
  • 面试官喜欢看到这种清晰、稳定的写法

💡 小贴士:

在面试中,如果被问“能不能不用递归?”——立刻答:“当然可以,用迭代法原地反转即可。”


✅ 方法二:递归法(思维训练)

🎯 核心思想:

利用 递归的天然反向特性 来完成反转:

假设从 head->next 开始的子链表已经被成功反转,那么:

  • 新的头节点就是 reverseList(head->next) 返回的结果
  • 我们只需要让 head->next->next = head,即把当前节点接到后面
  • 并设置 head->next = nullptr 避免环路

🧠 递归逻辑拆解:

reverseList(head):
    if head == null or head->next == null:
        return head   // 基础情况:空链表或单节点
    else:
        newHead = reverseList(head->next)   // 先反转后面的
        head->next->next = head             // 把自己接上去
        head->next = nullptr                // 断开旧连接
        return newHead                      // 返回新的头

⚠️ 注意事项:

  • 递归深度等于链表长度 → 栈溢出风险
  • 必须手动断开 head->next,否则形成环!
  • 不适合超长链表(如 10^6 节点),但用于小规模数据或思维训练很有效

💡 面试加分点:

“虽然递归简洁优雅,但在生产环境中我会优先选择迭代法,因为它更稳定、无栈溢出风险。”


🧠 解题思路(分步解析)

📌 步骤 1:判断边界条件

if (!head || !head->next) return head;
  • 如果链表为空或只有一个节点,无需反转,直接返回。

📌 步骤 2:初始化三指针

ListNode* prev = nullptr;
ListNode* curr = head;
  • prev 指向已反转部分的最后一个节点(初始为空)
  • curr 指向待处理的当前节点

📌 步骤 3:循环处理每个节点

while (curr) {
    ListNode* next = curr->next;     // 保存下一个节点
    curr->next = prev;               // 反转指针
    prev = curr;                     // 更新 prev
    curr = next;                     // 更新 curr
}
  • 每轮将当前节点插入到 prev 前面
  • 最终 prev 指向新链表的头节点

📌 步骤 4:返回结果

return prev;
  • 因为 prev 是最后一个被处理的节点,也就是新链表的头

📊 算法分析

方法时间复杂度空间复杂度是否原地是否推荐
迭代O(n)O(1)✅ 是✅ 强烈推荐
递归O(n)O(n)❌ 否仅用于学习

📌 面试考察维度:

  1. 基础能力:是否能正确处理链表指针
  2. 边界思维:是否考虑空链表、单节点等情况
  3. 空间意识:能否写出 O(1) 的解法
  4. 递归理解:是否掌握递归的“先深入再返回”机制
  5. 工程判断:知道何时该用迭代 vs 递归

💻 代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

// 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) {
        ListNode* prev = nullptr;
        ListNode* curr = head;
        while (curr) {
            ListNode* next = curr->next;      // 保存下一个节点,防止断链
            curr->next = prev;                // 反转当前节点的指针
            prev = curr;                      // 移动 prev 指针
            curr = next;                      // 移动 curr 指针
        }
        return prev;                          // prev 是新链表的头
    }

    // 方法二:递归法(思维训练)
    ListNode* reverseListRecursive(ListNode* head) {
        if (!head || !head->next) {
            return head;
        }
        ListNode* newHead = reverseListRecursive(head->next); // 先反转后面的部分
        head->next->next = head;                              // 将当前节点接到后面
        head->next = nullptr;                                 // 断开原连接,避免环
        return newHead;
    }
};

// 测试
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    // 构造测试链表: 1 -> 2 -> 3 -> 4 -> 5
    ListNode* head = new ListNode(1);
    head->next = new ListNode(2);
    head->next->next = new ListNode(3);
    head->next->next->next = new ListNode(4);
    head->next->next->next->next = new ListNode(5);

    Solution sol;
    
    // 测试迭代法
    ListNode* reversed = sol.reverseList(head);
    cout << "反转后链表: ";
    while (reversed) {
        cout << reversed->val << " ";
        reversed = reversed->next;
    }
    cout << endl;

    // 清理内存(实际项目中需注意)
    // 这里省略,仅作演示

    return 0;
}

✅ 输出结果:5 4 3 2 1

🔧 注意:实际使用时请记得释放动态分配的内存,避免内存泄漏。


🌟 本期完结,下期见!🔥

👉 点赞收藏加关注,新文更新不迷路。关注专栏【算法】LeetCode Hot100刷题日记,持续为你拆解每一道热题的底层逻辑与面试技巧!

💬 欢迎留言交流你的解法或疑问!一起进步,冲向 Offer!💪


📣 下一期预告:LeetCode 热题 100 第4题 —— 234. 回文链表(简单)

🔹 题目:给定一个单链表,判断它是否为回文结构(正读反读相同)。

🔹 核心思路

  • 使用 快慢指针 找到链表中点
  • 反转后半部分链表
  • 比较前后两部分是否相等
  • 恢复链表结构(可选,取决于要求)

🔹 考点

  • 快慢指针(寻找中点)
  • 链表反转(复用本题技巧)
  • 空间优化(O(1) vs O(n))
  • 边界处理(奇偶长度)

🔹 难度:中等偏上,但属于经典链表问题,面试必考!

💡 提示:不要用数组存储所有值,那样空间复杂度为 O(n),不够优雅!

📌 记住:当你在刷题时,不要只看答案,要像写这篇文章一样,深入思考每一步背后的原理、优化空间和面试价值。这才是真正提升算法能力的方式!


📌 附注:本文内容基于 LeetCode 官方题解与常见面试经验整理,适用于准备技术面试的开发者。建议结合手写模拟 + 白板推导练习,巩固记忆。

🌟 结语:从“会做”到“讲透”,才是算法的真正通关

刷题不是为了背答案,而是为了在面试官问“为什么这样写?”时,你能从容不迫地拆解每一步设计背后的权衡与智慧。
反转链表看似简单,却浓缩了指针操作的精髓、递归思维的优雅、空间优化的工程意识——这正是大厂考察的核心:你是否具备将问题抽象、分解并高效实现的能力

今天你掌握的不仅是一道题,而是一把钥匙:
🔑 它能打开“K 个一组翻转链表”的大门,
🔑 能助你轻松应对“判断回文链表”的变体,
🔑 更能在系统设计中让你对数据结构的操作游刃有余。

所以,请带着这份理解继续前行。
每一道 Hot100,都是通往 Offer 的一块基石。

💬 你在面试中被问过这道题吗?有没有更巧妙的解法?欢迎在评论区分享你的故事或疑问!
👉 关注专栏【算法】LeetCode Hot100刷题日记,我们下期一起拆解 回文链表 的双指针+反转技巧!

🔥 刷百题如破一关,悟一理可通万法。坚持,你离理想 Offer 只差 100 道题的距离。