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

LeetCode 21:合并两个有序链表

讲解 LeetCode 第 21 题合并两个有序链表的解法。主要包含两种思路:尾插法和哨兵位头结点法。通过遍历两个升序链表,将较小节点依次插入新链表,时间复杂度 O(n),空间复杂度 O(1)。提供了 C++ 代码实现及内存管理优化建议。

链路追踪发布于 2026/3/30更新于 2026/7/2553 浏览
LeetCode 21:合并两个有序链表

题目描述

题目链接:https://leetcode-cn.com/problems/merge-two-sorted-lists/description/

图 1

图 2

题目与思路分析

目标分析:

  1. 将两个升序链表合并为一个升序链表
  2. 返回新链表的头指针
  3. 新链表中的结点由已有两链表中的节点组成
  4. 提高要求:时间复杂度为 O(n),空间复杂度为 O(1)

思路一:尾插

思路:创建一个新的空链表 newHead,同时逐个遍历两个链表的结点,将 val 较小的节点尾插到新链表中。

操作:

  • 注意对空链表的处理,两个链表可能都为空,也可能任意一个为空。
  • 遍历:循环继续的条件为 curNode1 && curNode2,只要有一个链表结束,结束即可,将未遍历完的链表直接整体尾插到新链表中。
    • curNode1 和 curNode2:分别用于遍历链表一和链表二
    • tailNode:尾结点,方便尾插时找尾
  • curNode1->val <= curNode2->val 时,说明:curNode1 需要被尾插到新链表。
    • 第一次尾插时 (if(newHead == nullptr) 需要特殊处理:
      • tailNode = newHead = curNode;
      • curNode = curNode->next;
    • 其余结点的尾插,常规化处理:
      • tailNode->next = curNode;
      • tailNode = tailNode->next;
    • 插入完后:curNode = curNode->next;
  • curNode1->val > curNode2->val 时和上面是一样的逻辑。
  • 循环结束后,检查哪个链表未遍历完全,直接整体尾插到新链表中。
// 循环结束后,可能链表还有剩余元素
if(curNode1) tailNode->next = curNode1;
if(curNode2) tailNode->next = curNode2;

图 3

思路二:哨兵位优化

思路:使用带哨兵位的头结点优化尾插,在带哨兵位的链表中进行尾插时,无需特殊处理第一次尾插时的情况。

操作:

  • 注意对空链表的处理,两个链表可能都为空,也可能任意一个为空。
  • 遍历:循环继续的条件为 curNode1 && curNode2,只要有一个链表结束,结束即可,将未遍历完的链表直接整体尾插到新链表中。
    • curNode1 和 curNode2:分别用于遍历链表一和链表二
    • tailNode:尾结点,方便尾插时找尾
  • curNode1->val <= curNode2->val 时,说明:curNode1 需要被尾插到新链表。
    • 有了哨兵位的头结点,结点的尾插,都能常规化处理:
      • tailNode->next = curNode;
      • tailNode = tailNode->next;
    • 插入完后:curNode = curNode->next;
  • curNode1->val > curNode2->val 时和上面是一样的逻辑。
  • 循环结束后,检查哪个链表未遍历完全,直接整体尾插到新链表中。
  • 保存新链表的头结点:ListNode* newHead = guardNode->next,为 guardNode 的 next 结点。
    • 释放 guardNode,防止内存泄露。
  • 最终返回新的头结点 return newHead;
guardNode->next = nullptr;
delete guardNode;

图 4

代码实现

思路一

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        // 空链表判断
        if (list1 == nullptr && list2 == nullptr)
            return nullptr;
        if (list1 == nullptr || list2 == nullptr) {
            if (list1) return list1;
            if (list2) return list2;
        }
        // 以下 两个链表都不为空
        ListNode* curNode1 = list1;
        ListNode* curNode2 = list2;
        ListNode* newHead = nullptr, *tailNode = nullptr;
        while (curNode1 && curNode2) {
            // 更小的元素尾插到新结点
            if (curNode1->val <= curNode2->val) {
                // 第一个节点直接尾插
                if (newHead == nullptr) {
                    newHead = tailNode = curNode1;
                }
                // 其他节点直接 尾插
                else {
                    tailNode->next = curNode1;
                    tailNode = tailNode->next;
                }
                curNode1 = curNode1->next;
            } else {
                // 第一个节点直接尾插
                if (newHead == nullptr) {
                    newHead = curNode2;
                    tailNode = curNode2;
                }
                // 其他节点直接 尾插
                else {
                    tailNode->next = curNode2;
                    tailNode = tailNode->next;
                }
                curNode2 = curNode2->next;
            }
        }
        // 循环结束后,可能链表还有剩余元素
        if (curNode1) tailNode->next = curNode1;
        if (curNode2) tailNode->next = curNode2;
        return newHead;
    }
};

思路二

// 使用带哨兵位的头结点 优化算法
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        if (list1 == nullptr) return list2;
        if (list2 == nullptr) return list1;
        
        ListNode* curNode1 = list1, *curNode2 = list2;
        // 遍历链表,用于迭代
        // 尾插需要找尾,提前保存,避免重复找
        ListNode* guardNode = new ListNode();
        ListNode* tailNode = guardNode;
        
        while (curNode1 && curNode2) {
            // 将小的那个结点,尾插到 guardNode 后面
            if (curNode1->val <= curNode2->val) {
                tailNode->next = curNode1;
                tailNode = tailNode->next;
                curNode1 = curNode1->next;
            } else {
                tailNode->next = curNode2;
                tailNode = tailNode->next;
                curNode2 = curNode2->next;
            }
        }
        // 有一个链表结束后,将另一个链表再链接上
        if (curNode1) tailNode->next = curNode1;
        if (curNode2) tailNode->next = curNode2;
        
        // 保存新的头结点,并释放内存,防止内存泄露
        ListNode* newHead = guardNode->next;
        guardNode->next = nullptr;
        delete guardNode;
        
        // 返回新结点
        return newHead;
    }
};

算法代码优化

优化思路一

  • 优化了空链表返回的逻辑
  • 其中一个链表为空,就返回另一个链表
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        if (list1 == nullptr) return list2;
        if (list2 == nullptr) return list1;
        
        // 以下 两个链表都不为空
        ListNode* curNode1 = list1;
        ListNode* curNode2 = list2;
        ListNode* newHead = nullptr, *tailNode = nullptr;
        while (curNode1 && curNode2) {
            // 更小的元素尾插到新结点
            if (curNode1->val <= curNode2->val) {
                // 第一个节点直接尾插
                if (newHead == nullptr) {
                    newHead = tailNode = curNode1;
                }
                // 其他节点直接 尾插
                else {
                    tailNode->next = curNode1;
                    tailNode = tailNode->next;
                }
                curNode1 = curNode1->next;
            } else {
                // 第一个节点直接尾插
                if (newHead == nullptr) {
                    newHead = curNode2;
                    tailNode = curNode2;
                }
                // 其他节点直接 尾插
                else {
                    tailNode->next = curNode2;
                    tailNode = tailNode->next;
                }
                curNode2 = curNode2->next;
            }
        }
        // 循环结束后,可能链表还有剩余元素
        if (curNode1) tailNode->next = curNode1;
        if (curNode2) tailNode->next = curNode2;
        return newHead;
    }
};

优化思路二

  • 优化了初始链表判空的处理
  • 这里无需对空链表进行处理:通过分析得知
    • 当 list1 或 list2 为 nullptr 时,不会进入 while 循环。由于 guardNode 非空, ListNode* newHead = guardNode->next,因此保存的 newHead 时一定合法。
    • 当 list1 或 list2 全为 nullptr 时,newHead 即为 nullptr
    • 当 list1 或 list2 其中一个为 nullptr 时,newHead 即另一个链表的头结点
// 使用带哨兵位的头结点 优化算法
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        // 遍历链表的结点
        ListNode* curNode1 = list1, *curNode2 = list2;
        // 尾插需要找尾,提前保存,避免重复找
        ListNode* guardNode = new ListNode();
        ListNode* tailNode = guardNode;
        
        while (curNode1 && curNode2) {
            // 将小的那个结点,尾插到 guardNode 后面
            if (curNode1->val <= curNode2->val) {
                tailNode->next = curNode1;
                tailNode = tailNode->next;
                curNode1 = curNode1->next;
            } else {
                tailNode->next = curNode2;
                tailNode = tailNode->next;
                curNode2 = curNode2->next;
            }
        }
        // 有一个链表结束后,将另一个链表再链接上
        if (curNode1) tailNode->next = curNode1;
        if (curNode2) tailNode->next = curNode2;
        
        // 保存新的头结点,并释放内存,防止内存泄露
        ListNode* newHead = guardNode->next;
        guardNode->next = nullptr;
        delete guardNode;
        
        // 返回新结点
        return newHead;
    }
};

目录

  1. 题目描述
  2. 题目与思路分析
  3. 思路一:尾插
  4. 思路二:哨兵位优化
  5. 代码实现
  6. 思路一
  7. 思路二
  8. 算法代码优化
  9. 优化思路一
  10. 优化思路二
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

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

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

更多推荐文章

查看全部
  • Java 基础:MapStruct 使用指南与原理剖析
  • AI 全自动科研系统与 AIGC 动画电影的技术突破与架构解析
  • 大模型入门:程序员为什么要学习大模型应用开发
  • OpenClaw开源AI智能体框架及可持续变现实践
  • Python 使用 MCP 客户端调用高德地图服务查询天气
  • 高德地图离线部署方案:获取瓦片数据与私有化调用
  • Arduino BLDC 自主巡逻机器人:避障与路径规划实战
  • FastGPT 结合 MCP 协议实现工具增强型智能体构建
  • Hello-Algo 本地部署及 cpolar 公网访问教程
  • OpenClaw 技术解析:AI 智能体的能力、局限与安全隐忧
  • 基于 DeepSeek 的贪吃蛇游戏开发实战
  • 使用 Dify 搭建企业知识库聊天机器人
  • AI Agent Skills 资源合集:支持 Cursor、Claude Code 与 Copilot
  • Python 中的多线程是什么?如何实现?
  • OpenHarmony 跨端生态适配指南:Flutter/RN/C/C++/仓颉鸿蒙化方案
  • OpenClaw 接入飞书实战:让 AI 机器人直接操作文档与表格
  • C++ 搜索引擎 Searcher 模块源码解析:正倒排索引实现
  • RWKV 模型深度解析:融合 RNN 与 Transformer 架构优势
  • Gazebo 机器人三维物理仿真平台核心解析
  • OpenClaw 钉钉机器人配置指南(macOS)

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如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