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. 排序链表

  • 归并排序链表

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


相关推荐
Hello:CodeWorld31 分钟前
C 风格变参 vs C++ 变参模板:核心区别与选型指南
c语言·c++·算法
8Qi82 小时前
LeetCode 516:最长回文子序列
算法·leetcode·职场和发展·动态规划
十月的皮皮2 小时前
C语言学习笔记20260606- 求月份天数三种写法
c语言·笔记·学习
搬砖魁首3 小时前
基础能力系列 - 多线程2 - 条件变量
c++·rust·条件变量·原子类型·线程同步互斥
youngerwang3 小时前
【从搬运工到协处理器:网卡芯片架构、算法、验证与边缘演进深度剖析】
网络·算法·架构·芯片
chase_my_dream3 小时前
C++ + SLAM 高频面试问题整理
开发语言·c++·面试
想要成为糕糕手3 小时前
前端必修课:JavaScript 数组与数据结构底层逻辑全解析
javascript·数据结构·面试
牛油果子哥q3 小时前
【C++ STL string 】C++ STL string 终极精讲:底层原理、内存机制、全套API、深浅拷贝、易错坑点与工程实战规范
数据库·c++
KaMeidebaby3 小时前
卡梅德生物技术快报|纯化重组蛋白实操详解
人工智能·python·tcp/ip·算法·机器学习
caimouse3 小时前
Reactos 第 5 章 进程与线程 — 5.8 Windows 的 APC 机制
c语言·windows