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

  • 归并排序链表

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


相关推荐
计算机安禾3 小时前
【c++面向对象编程】第25篇:仿函数(函数对象):重载operator()
开发语言·c++·算法
kkeeper~3 小时前
0基础C语言积跬步之深入理解指针(4)
c语言·开发语言
周末也要写八哥4 小时前
在C++中使用预定义宏
开发语言·c++·算法
学会870上岸华师4 小时前
C 语言程序设计——第一章课后编程题
c语言·开发语言·学习·算法
小小编程路4 小时前
新手快速学 Python 极简速成指南
开发语言·c++·python
小马过河R4 小时前
RAG检索优化策略:系统性四层框架解析
人工智能·python·算法·ai·llm·rag·问答
AI技术控4 小时前
论文解读:AE-TCN-SA——基于自编码器、TCN 与自注意力机制的锂电池内短路诊断方法
人工智能·python·深度学习·算法·机器学习·自然语言处理
ʚ希希ɞ ྀ4 小时前
动态规划基础知识---爬楼梯
算法·动态规划
宏笋5 小时前
C++ 约束模板参数Concepts详解
c++
计算机安禾5 小时前
【c++面向对象编程】第26篇:对象的内存模型:成员变量与成员函数的存储分离
开发语言·c++·算法