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

  • 归并排序链表

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


相关推荐
条tiao条2 小时前
从 “猜数字游戏” 入门 BST:C 语言从零实现与核心操作
c语言·网络·游戏
挠头猴子2 小时前
c++中常用的运算符优先级
java·开发语言·c++
阿里嘎多哈基米2 小时前
速通Hot100-Day04——哈希
数据结构·算法·leetcode·哈希算法·散列表
Yupureki2 小时前
《C++实战项目-高并发内存池》5.PageCache构造
c语言·开发语言·c++·单例模式·github
WolfGang0073212 小时前
代码随想录算法训练营 Day10 | 栈与队列 part02
数据结构
飞天狗1112 小时前
最短路算法
算法
汉克老师2 小时前
GESPC++考试五级语法知识(二、埃氏筛与线性筛)课后习题
算法·线性筛·素数·gesp5级·gesp五级·埃氏筛·筛法
xsyaaaan2 小时前
leetcode-hot100-普通数组:53最大子数组和-56合并区间-189轮转数组-238除了自身以外数组的乘积-41缺失的第一个正数
leetcode
Qt学视觉2 小时前
AI3-PaddleOCR搭建环境1
c++·人工智能·opencv·ocr·paddlepaddle