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

一、题目描述

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

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

示例 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

  • l1 和 l2 均按 非递减顺序 排列


二、解题思路

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

思路如下:

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

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

  3. 同时遍历 list1 和 list2:

    • 比较两个节点的值

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

    • 移动对应链表指针

  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)
  • n 为 list1 的长度

  • m 为 list2 的长度

每个节点最多访问一次。


空间复杂度

复制代码
O(1)

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


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

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

例如:

复制代码
head = list1 or list2 ?

代码逻辑会变复杂。

使用虚拟头节点后:

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

最终返回:

复制代码
dummy.next

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


六、递归解法(扩展)

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

思路:

  • 如果 list1 较小,则 list1->next 与 list2 继续合并

  • 如果 list2 较小,则 list2->next 与 list1 继续合并

代码如下:

复制代码
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. 排序链表

  • 归并排序链表

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


相关推荐
启观川8 小时前
数据结构与算法 -第 3 章 常用算法-动态规划
数据结构·笔记·python·算法
码爸8 小时前
排序算法介绍
python·算法·排序算法
sm_926787058 小时前
RFID 标签打印的技术实现要点与二次开发实践
java·大数据·前端·c++·编辑器
by209998 小时前
C++ 多态:同一个基类接口,调用不同的函数
开发语言·c++·经验分享·多态
colinu8 小时前
C++ 异步任务与线程生命周期:`std::async`、`future`、`thread`、`jthread` 与协作式退出
java·jvm·c++
动词ing9 小时前
【题目练习】算法 组合总和||
算法
wuminyu9 小时前
ForkJoinPool窃取线程在扫描其他WorkQueue时的随机采样算法与Phase线程挂起唤醒机制解析
java·linux·c语言·jvm·c++
longlongzihan9 小时前
链表找环:Floyd 判圈算法(LeetCode 141 & 142 )
c++·算法·leetcode·链表
专业程序开发源9 小时前
flask家电故障预测系统92491-计算机课程设计/毕业设计
vscode·python·sql·算法·flask·课程设计
liulilittle9 小时前
Linux 下 select 测试函数
linux·服务器·网络·数据库·c++·select·c