LeetCode 21. 合并两个有序链表(C语言详解 | 链表经典题)

一、题目描述

给定两个 按非递减顺序排列 的链表 list1list2,将它们合并为一个新的 升序链表 并返回。

新链表是通过 拼接给定的两个链表的所有节点组成的

示例 1:

复制代码
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]

示例 2:

复制代码
输入:l1 = [], l2 = []
输出:[]

示例 3:

复制代码
输入:l1 = [], l2 = [0]
输出:[0]

提示:

  • 两个链表的节点数目范围是 [0, 50]

  • -100 <= Node.val <= 100

  • l1l2 均按 非递减顺序 排列


二、解题思路

由于两个链表已经是 有序链表 ,我们可以使用 双指针思想 来解决。

思路如下:

  1. 创建一个 虚拟头节点 dummy,用于简化链表操作。

  2. 定义一个指针 cur 指向当前新链表的末尾。

  3. 同时遍历 list1list2

    • 比较两个节点的值

    • 将较小的节点接到新链表后面

    • 移动对应链表指针

  4. 当其中一个链表遍历结束时,直接把 剩余链表接到新链表末尾

示意过程:

复制代码
list1: 1 -> 2 -> 4
list2: 1 -> 3 -> 4

比较过程:

1 vs 1  -> 取 list1
2 vs 1  -> 取 list2
2 vs 3  -> 取 list1
4 vs 3  -> 取 list2
4 vs 4  -> 取 list1
剩余 -> 接上 list2

最终结果:

复制代码
1 -> 1 -> 2 -> 3 -> 4 -> 4

三、C语言代码实现

复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {

    // 创建虚拟头节点
    struct ListNode dummy;
    struct ListNode *cur = &dummy;
    dummy.next = NULL;

    // 同时遍历两个链表
    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;
    }

    // 连接剩余链表
    if(list1 != NULL)
        cur->next = list1;
    else
        cur->next = list2;

    return dummy.next;
}

四、复杂度分析

时间复杂度

复制代码
O(n + m)
  • nlist1 的长度

  • mlist2 的长度

每个节点最多访问一次。


空间复杂度

复制代码
O(1)

只使用了几个指针变量,没有额外空间开销。


五、为什么使用虚拟头节点?

如果不使用 dummy,新链表的 第一个节点需要单独处理

例如:

复制代码
head = list1 or list2 ?

代码逻辑会变复杂。

使用虚拟头节点后:

复制代码
dummy -> 1 -> 1 -> 2 -> 3 -> 4 -> 4

最终返回:

复制代码
dummy.next

这样可以统一处理所有节点,代码更加简洁清晰。


六、递归解法(扩展)

除了迭代方法,还可以使用 递归 来实现。

思路:

  • 如果 list1 较小,则 list1->nextlist2 继续合并

  • 如果 list2 较小,则 list2->nextlist1 继续合并

代码如下:

复制代码
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(1)

这道题也是很多链表题目的基础,例如:

  • 23. 合并 K 个升序链表

  • 148. 排序链表

  • 归并排序链表

掌握这道题,对于理解 链表合并思想 非常重要。


相关推荐
致Great2 小时前
科研人的 AI,不该只回答问题:我用字节TraeWork 跑了一遍真实研究任务
算法
纵有疾風起2 小时前
线性表的定义与基本操作 — 从逻辑结构到 ADT 接口
数据结构·算法·408·线性表·adt
charlie1145141913 小时前
Cinux · 第一次跳进 Ring 3:用户态与特权隔离
开发语言·c++·操作系统·开源项目
别动我齐刘海3 小时前
机器学习基础2——C++、OpenCV、点云、Open3D
c++·人工智能·opencv·机器学习·计算机视觉·机器人·ros2
wuyk5553 小时前
1.栈:后进先出的线性数据结构
c语言·数据结构·stm32·单片机
测试_AI_一辰3 小时前
AI Agent 评测最隐蔽的坑-记忆
人工智能·算法·ai·自动化·ai编程
躺不平的理查德3 小时前
Windows C++ 第三方库使用流程备忘录--OpenCV
开发语言·c++
余额瞒着我当琳3 小时前
C++STL容器string--迭代器,string的接口,string的遍历,访问方式
c++
郭涤生3 小时前
rootfs 详解与裁剪优化记录
linux·c++·bsp
lucas_AI3 小时前
给YOLO检测器插 LoRA,'插对地方'比'插什么'更要命
人工智能·算法