跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C算法

链表核心算法实战:LeetCode Hot 100 经典题目解析

聚焦链表核心算法,针对 LeetCode Hot 100 中的四道高频题进行实战解析。涵盖反转链表、环形链表检测、合并有序链表及删除倒数第 N 个节点。深入剖析迭代与递归两种解法,重点讲解双指针技巧、虚拟头节点应用及边界条件处理。通过代码实现与复杂度分析,帮助读者掌握链表操作精髓,提升面试解题能力。

RedisGeek发布于 2026/3/22更新于 2026/5/2222 浏览
链表核心算法实战:LeetCode Hot 100 经典题目解析

在这里插入图片描述

🏠个人主页:黎雁
🎬作者简介:C/C++/JAVA 后端开发学习者
❄️个人专栏:C 语言、数据结构(C 语言)、EasyX、游戏、规划、程序人生
✨ 从来绝巘须孤往,万里同尘即玉京

在这里插入图片描述

线性表系列终篇:链表试炼与 LeetCode 实战

大家好,这是线性表系列的收官之作。经过前三篇的系统学习,我们已经从理论到实践,完整地掌握了单向链表和双向带头循环链表的核心知识。从理解'物理连续'到'逻辑连续'的思维转变,到手写代码攻克指针难题,再到领略数据结构设计的优雅哲学,你已经完成了从新手到高手的蜕变。

但真正的试炼,才刚刚开始。今天,我们将不再局限于基础操作的实现,而是将目光投向 LeetCode Hot 100 中的链表经典题目。这些题目是检验你对链表理解深度、算法思维和编码能力的试金石,也是面试中高频出现的'拦路虎'。

准备好了吗?让我们迎接最终的挑战!⚔️


一、解题前的核心技巧回顾

在进入具体题目之前,我们先来回顾一下解决链表问题的几个核心技巧,它们将是你手中最强大的武器:

  1. 双指针法 (Two Pointers):这是链表问题中最常用、最高效的技巧。通过设置快慢指针、前后指针、间隔指针等,可以解决很多看似复杂的问题,如找中点、判断环、删除倒数第 N 个节点等。
  2. 虚拟头节点 (Dummy Head Node):类似于我们实现的双向链表的哨兵位,它可以完美解决头节点可能被删除的边界问题,让代码逻辑更加统一和简洁。
  3. 递归 (Recursion):链表的天然递归结构(node.next 也是一个链表)使得递归成为一种优雅的解法,尤其适用于反转、合并等问题。
  4. 画图辅助 (Draw a Picture):当指针关系变得复杂时,动手在纸上画出节点和指针的指向变化,是理清思路、避免错误的最佳方法。
  5. 边界条件优先处理:链表类题目最容易出错的地方就是边界,解题时优先处理 head == NULL、head->next == NULL 等特殊情况。

二、经典题目实战解析

题目一:反转链表 (LeetCode 206)

题目描述:给你单链表的头节点 head,请你反转链表,并返回反转后的链表。 示例:输入 head = [1,2,3,4,5],输出 [5,4,3,2,1] 难度:简单 考察频率:⭐⭐⭐⭐⭐ (面试必考题)

解法一:迭代(双指针)

思路解析: 使用两个指针 prev 和 cur。prev 初始为 NULL,cur 初始为 head。在遍历链表时,将 cur 的 next 指针指向 prev,然后 prev 和 cur 同时向后移动。当 cur 为 NULL 时,prev 就是新的头节点。

注意:必须提前保存 cur->next,否则反转指针后会丢失后续链表。

struct ListNode* reverseList(struct ListNode* head) {
    // 处理空链表和单节点链表
    if (head == NULL || head->next == NULL) {
        return head;
    }
    struct ListNode* prev = NULL;
    struct ListNode* cur = head;
    while (cur != NULL) {
        struct ListNode* next = cur->next; // 保存下一个节点,防止链表断裂
        cur->next = prev;                  // 反转当前节点的指针
        prev = cur;                        // prev 指针后移
        cur = next;                        // cur 指针后移
    }
    return prev; // prev 成为新的头节点
}

复杂度分析:

  • 时间复杂度:O(N),其中 N 是链表的长度。需要遍历链表一次。
  • 空间复杂度:O(1),只使用了几个指针变量,属于原地反转。

边界测试用例:

  • 输入 NULL → 输出 NULL
  • 输入 [1] → 输出 [1]
解法二:递归

思路解析: 递归的核心思想是'大事化小'。假设链表的后半部分已经被反转,我们只需要处理当前节点和它后面的节点。

  1. 递归终止条件:head 为 NULL 或 head->next 为 NULL。
  2. 递归调用 reverseList(head->next),得到反转后的新头节点 newHead。
  3. 将当前节点的下一个节点的 next 指向自己,即 head->next->next = head。
  4. 将当前节点的 next 指向 NULL,防止链表成环。
  5. 返回新头节点 newHead。
struct ListNode* reverseList(struct ListNode* head) {
    // 递归终止条件:空链表 或 只有一个节点
    if (head == NULL || head->next == NULL) {
        return head;
    }
    // 递归调用,反转 head 之后的所有节点
    struct ListNode* newHead = reverseList(head->next);
    // 反转当前节点与下一个节点的指向
    head->next->next = head;
    head->next = NULL; // 防止链表成环,这一步是关键
    return newHead;
}

复杂度分析:

  • 时间复杂度:O(N),需要递归 N 次,遍历所有节点。
  • 空间复杂度:O(N),递归调用栈的深度为 N,最坏情况下链表退化为一条链。

解法对比:

解法优点缺点
迭代空间复杂度低,原地反转思路相对抽象
递归代码简洁优雅,符合链表特性空间复杂度高,存在栈溢出风险

题目二:环形链表 (LeetCode 141)

题目描述:给你一个链表的头节点 head,判断链表中是否有环。如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 示例:输入 head = [3,2,0,-4], pos = 1 (链表尾部连接到第二个节点),输出 true 难度:简单 考察频率:⭐⭐⭐⭐

解法:快慢指针(Floyd 判圈算法)

思路解析: 想象在环形跑道上跑步的场景:一个跑得快的运动员和一个跑得慢的运动员,如果跑道是环形的,快的运动员最终一定会追上慢的运动员;如果是直线跑道,快的运动员会先到达终点。

  • 设置慢指针 slow:每次向后移动 1 步。
  • 设置快指针 fast:每次向后移动 2 步。
  • 有环情况:fast 指针会追上 slow 指针,此时 slow == fast。
  • 无环情况:fast 指针会先到达链表末尾(fast == NULL 或 fast->next == NULL)。
bool hasCycle(struct ListNode* head) {
    // 处理空链表和单节点链表
    if (head == NULL || head->next == NULL) {
        return false;
    }
    struct ListNode* slow = head;
    struct ListNode* fast = head->next; // 初始错开,避免直接相等
    while (slow != fast) {
        // 快指针到达终点,无环
        if (fast == NULL || fast->next == NULL) {
            return false;
        }
        slow = slow->next;      // 慢指针走一步
        fast = fast->next->next; // 快指针走两步
    }
    // 快慢指针相遇,有环
    return true;
}

关键细节:

  • 快指针初始化为 head->next,而不是 head,避免循环一开始就满足 slow == fast。
  • 循环条件必须判断 fast 和 fast->next 是否为空,防止空指针访问。

复杂度分析:

  • 时间复杂度:O(N)。有环时,快慢指针相遇时,慢指针走过的距离不会超过链表总长度;无环时,快指针遍历链表一次。
  • 空间复杂度:O(1),只使用了两个指针变量,无需额外空间。

拓展思路:哈希表法 可以使用哈希表存储访问过的节点,遍历链表时如果遇到重复节点则说明有环。但该方法空间复杂度为 O(N),不如快慢指针法高效。


题目三:合并两个有序链表 (LeetCode 21)

题目描述:将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例:输入 l1 = [1,2,4], l2 = [1,3,4],输出 [1,1,2,3,4,4] 难度:简单 考察频率:⭐⭐⭐⭐⭐

解法一:迭代(虚拟头节点)

思路解析: 这是一个典型的归并操作。直接操作两个链表的头节点会面临边界问题(比如某个链表为空),因此我们引入虚拟头节点 dummy,用 cur 指针构建新链表。

  1. 创建虚拟头节点 dummy,cur 指针初始指向 dummy。
  2. 循环比较两个链表的当前节点值,将较小值的节点连接到 cur 后面。
  3. 移动对应链表的指针和 cur 指针。
  4. 当其中一个链表遍历完后,将另一个链表的剩余部分直接连接到 cur 后面。
  5. 返回 dummy->next 作为新链表的头节点。
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
    // 创建虚拟头节点,简化边界处理
    struct ListNode dummy;
    dummy.next = NULL;
    struct ListNode* cur = &dummy;
    // 同时遍历两个链表
    while (list1 != NULL && list2 != NULL) {
        if (list1->val < list2->val) {
            cur->next = list1;
            list1 = list1->next;
        } else {
            cur->next = list2;
            list2 = list2->next;
        }
        cur = cur->next; // cur 指针后移
    }
    // 连接剩余的节点
    cur->next = (list1 != NULL) ? list1 : list2;
    return dummy.next;
}

复杂度分析:

  • 时间复杂度:O(N + M),其中 N 和 M 分别是两个链表的长度,需要遍历所有节点。
  • 空间复杂度:O(1),只使用了虚拟头节点和几个指针,原地合并。
解法二:递归

思路解析: 递归的核心是每次选择两个链表中较小的头节点,然后递归合并剩余的部分。

  1. 终止条件:如果 list1 为空,返回 list2;如果 list2 为空,返回 list1。
  2. 比较 list1 和 list2 的头节点值,选择较小的作为当前节点。
  3. 递归合并剩余的链表,并将结果连接到当前节点的后面。
  4. 返回当前节点。
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
    // 终止条件:一个链表为空,返回另一个链表
    if (list1 == NULL) {
        return list2;
    }
    if (list2 == NULL) {
        return list1;
    }
    // 选择较小的节点作为当前节点
    if (list1->val < list2->val) {
        list1->next = mergeTwoLists(list1->next, list2);
        return list1;
    } else {
        list2->next = mergeTwoLists(list1, list2->next);
        return list2;
    }
}

复杂度分析:

  • 时间复杂度:O(N + M),需要递归合并所有节点。
  • 空间复杂度:O(N + M),递归调用栈的深度为两个链表的长度之和。

题目四:删除链表的倒数第 N 个结点 (LeetCode 19)

题目描述:给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。 示例:输入 head = [1,2,3,4,5], n = 2,输出 [1,2,3,5] 难度:中等 考察频率:⭐⭐⭐⭐⭐

解法:双指针(间隔法)

思路解析: 删除倒数第 n 个节点,关键是找到倒数第 n+1 个节点(目标节点的前驱节点)。使用双指针法可以在一次遍历中完成:

  1. 创建虚拟头节点 dummy,指向 head,避免删除头节点的边界问题。
  2. 定义快慢指针 fast 和 slow,初始都指向 dummy。
  3. 先让 fast 指针向前移动 n+1 步,使 fast 和 slow 之间间隔 n 个节点。
  4. 然后让 fast 和 slow 同时向前移动,直到 fast 指向 NULL。
  5. 此时 slow 指向的就是倒数第 n+1 个节点,执行删除操作:slow->next = slow->next->next。
  6. 返回 dummy->next。
struct ListNode* removeNthFromEnd(struct ListNode* head, int n) {
    // 创建虚拟头节点
    struct ListNode dummy;
    dummy.next = head;
    struct ListNode* fast = &dummy;
    struct ListNode* slow = &dummy;
    // fast 指针先走 n+1 步
    for (int i = 0; i <= n; i++) {
        fast = fast->next;
    }
    // 快慢指针同时移动
    while (fast != NULL) {
        fast = fast->next;
        slow = slow->next;
    }
    // 删除倒数第 n 个节点
    struct ListNode* temp = slow->next; // 保存要删除的节点
    slow->next = slow->next->next;
    free(temp); // 释放内存,避免内存泄漏
    return dummy.next;
}

关键细节:

  • 虚拟头节点是必须的,否则当 n 等于链表长度时,无法删除头节点。
  • fast 指针需要移动 n+1 步,而不是 n 步,这样才能让 slow 停在目标节点的前驱。
  • 删除节点后要释放内存,避免内存泄漏。

复杂度分析:

  • 时间复杂度:O(N),只需要遍历链表一次。
  • 空间复杂度:O(1),使用常数级别的额外空间。

三、总结与后续挑战

恭喜你完成了本次链表王者试炼!通过对这四道经典题目的深入剖析,你不仅巩固了链表的基础知识,更重要的是掌握了双指针、虚拟头节点、递归等高级解题技巧,并学会了从不同角度思考问题,分析算法的优劣。

你已掌握的核心能力
  1. 算法思维:能够分析问题本质,选择最优的解题策略。
  2. 编码能力:能够将解题思路转化为清晰、高效、健壮的代码。
  3. 复杂度分析:能够评估算法的时间和空间效率,选择最优解。
  4. 边界处理:能够考虑到各种特殊情况,写出鲁棒性强的代码。
后续挑战(LeetCode Hot 100 链表高频题)

这几道题目只是链表领域的冰山一角,想要成为真正的'链表王者',还需要攻克以下进阶题目:

  • K 个一组翻转链表 (LeetCode 25):反转链表的进阶版,难度较大,面试高频硬核题。
  • 相交链表 (LeetCode 160):寻找两个链表的第一个公共节点,双指针法的巧妙应用。
  • 复制带随机指针的链表 (LeetCode 138):对链表和哈希表的综合考察,难度较高。
  • 排序链表 (LeetCode 148):要求 O(n log n) 时间复杂度和常数级空间复杂度,考验算法功底。

数据结构与算法的学习之路永无止境,没有捷径可走,唯有多敲代码、多画图、多思考,才能真正掌握其精髓。希望这个系列能成为你坚实的基石,祝你在未来的学习和面试中披荆斩棘,所向披靡!🚀

目录

  1. 线性表系列终篇:链表试炼与 LeetCode 实战
  2. 一、解题前的核心技巧回顾
  3. 二、经典题目实战解析
  4. 题目一:反转链表 (LeetCode 206)
  5. 解法一:迭代(双指针)
  6. 解法二:递归
  7. 题目二:环形链表 (LeetCode 141)
  8. 解法:快慢指针(Floyd 判圈算法)
  9. 题目三:合并两个有序链表 (LeetCode 21)
  10. 解法一:迭代(虚拟头节点)
  11. 解法二:递归
  12. 题目四:删除链表的倒数第 N 个结点 (LeetCode 19)
  13. 解法:双指针(间隔法)
  14. 三、总结与后续挑战
  15. 你已掌握的核心能力
  16. 后续挑战(LeetCode Hot 100 链表高频题)
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 后仿真 SDF 反标常见 Warning 分析与处理指南
  • OpenClaw 本地优先 AI 智能体:从安装到实战部署指南
  • Python 主流爬虫框架特性对比与选型指南
  • 阿里开源 Page-Agent:一行代码实现浏览器内 AI 原生应用
  • Git 原理与使用进阶:远程协作、标签管理及企业级开发模型
  • 无人机“黑飞”正式入法:2026 年 1 月 1 日起违规飞行将面临拘留
  • 无人机视觉任务常用数据集汇总:检测与分割资源整理
  • Rust 重构 Android 蓝牙协议栈:从 C++ 到安全高效
  • MySQL 与 MCP 集成:从环境构建到 AI 驱动的数据交互
  • Stack-Chan 机器人入门:基于 JavaScript 的 M5Stack 智能伙伴
  • Android 开发者成长路径与技术进阶指南
  • 开源模型首次击败 GPT-4 网页操作任务,7B 小模型获大模型级能力
  • 大模型 LLM 量化的 5 个基础技术知识
  • Python SQLAlchemy ORM 数据库操作实战指南
  • 基于 Python 的牙科全结构化录入与牙位可视化 AI 系统架构
  • 量子计算驱动的 Python 医疗诊断编程:变分量子分类器详解
  • JNI 开发陷阱:C++ Debug 正常为何 Release 返回 NaN
  • 数据结构:堆排序原理与实现
  • Spring 事务及其传播机制详解
  • Android 3D 模型查看器:STL OBJ PLY 格式支持与渲染实现

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online