C语言编程--20.合并K个升序列表

C语言编程--20.合并K个升序列表

题目:

给你一个链表数组,每个链表都已经按升序排列。

请你将所有链表合并到一个升序链表中,返回合并后的链表。

示例 1

输入:lists = [[1,4,5],[1,3,4],[2,6]]

输出:[1,1,2,3,4,4,5,6]

解释:链表数组如下:

1-\>4-\>5, 1-\>3-\>4, 2-\>6

将它们合并到一个有序链表中得到。

1->1->2->3->4->4->5->6

示例 2

输入:lists = []

输出:[]

示例 3

输入:lists = [[]]

输出:[]

提示

k == lists.length

0 <= k <= 10^4

0 <= lists[i].length <= 500

-10^4 <= lists[i][j] <= 10^4

lists[i] 按 升序 排列

lists[i].length 的总和不超过 10^4

复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* merge(struct ListNode* list_a, struct ListNode* list_b) {
    // 边界情况:若任一链表为空,直接返回非空链表
    if (list_a == NULL) return list_b;
    if (list_b == NULL) return list_a;

    struct ListNode* head;        // 合并后链表的头节点
    struct ListNode* tail;        // 合并后链表的尾节点

    // 确定头节点:选择list_a和list_b中值较小的节点作为头节点
    if (list_a->val > list_b->val) {
        head = list_b;
        list_b = list_b->next; // 移动list_b指针,跳过已选节点
    } else {
        head = list_a;
        list_a = list_a->next; // 移动list_a指针,跳过已选节点
    }
    tail = head; // 尾节点初始化为头节点

    // 遍历剩余节点,合并两个链表
    while (list_a && list_b) {
        if (list_a->val > list_b->val) {
            tail->next = list_b; // 将list_b当前节点接到tail之后
            list_b = list_b->next; // 移动list_b指针
        } else {
            tail->next = list_a; // 将list_a当前节点接到tail之后
            list_a = list_a->next; // 移动list_a指针
        }
        tail = tail->next; // 更新尾节点到新接入的节点
    }

    // 处理剩余节点:将未遍历完的链表直接接到tail之后
    if (list_a)
        tail->next = list_a;
    else
        tail->next = list_b;

    return head; // 返回合并后的链表头节点
}

/**
 * @brief 合并K个有序链表(分治递归法)
 * @param lists 链表数组,每个元素为有序链表的头节点(可能为空)
 * @param listsSize 链表数组的长度
 * @return 合并后的有序链表头节点
 * @note 算法思路:
 * 1. 递归终止条件:若链表数组只剩1个链表,直接返回该链表
 * 2. 分治策略:将链表数组分成两半,递归合并每一半,最后合并两个结果
 * 3. 临时数组temp存储合并后的子问题结果,处理奇偶长度数组的边界情况
 */
struct ListNode* mergeKLists(struct ListNode** lists, int listsSize) {
    // 边界情况:空数组直接返回NULL
    if (listsSize == 0) return NULL;
    // 递归终止条件:只剩一个链表,直接返回
    if (listsSize == 1) return lists[0];

    // 计算中间数组的大小(向上取整),例如:
    // listsSize=3 → num=2;listsSize=4 → num=2
    int num = (listsSize + 1) / 2;
    // 分配临时数组,存储两两合并后的子链表
    struct ListNode** temp = (struct ListNode**)malloc(sizeof(struct ListNode*) * num);
    
    int i = 0; // 原数组遍历指针
    int j = 0; // 临时数组存储指针

    // 两两合并链表,处理偶数长度部分
    while (i < listsSize - 1) {
        // 合并lists[i]和lists[i+1],存入temp[j]
        temp[j] = merge(lists[i], lists[i + 1]);
        i += 2; // 每次跳两个元素
        j++;    // 临时数组指针后移
    }

    // 处理奇数长度的剩余链表(若存在)
    if (listsSize % 2) { // 等价于 i == listsSize - 1
        // 将最后一个未配对的链表存入temp
        temp[j] = lists[listsSize - 1];
        j++;
    }

    // 递归合并临时数组中的子链表
    struct ListNode* result = mergeKLists(temp, j);
    // 释放临时数组内存,避免内存泄漏
    free(temp);
    
    return result;
}

代码优点分析:

  1. 分治递归思路清晰
    通过递归将合并 K 个链表的问题拆解为合并两个有序链表的子问题,符合分治算法的设计思想,代码结构层次分明。
  2. 边界情况处理全面
    空链表数组(listsSize == 0)直接返回NULL。
    单链表情况(listsSize == 1)直接返回原链表,避免无效递归。
    奇数长度数组通过listsSize % 2判断剩余链表,确保所有链表都被处理。
  3. 内存管理意识强
    在递归调用后释放临时数组temp的内存,避免内存泄漏,符合 C 语言动态内存管理规范。
  4. 合并逻辑高效
    merge函数使用双指针遍历两个链表,时间复杂度为 O (m+n)(m 和 n 为链表长度),空间复杂度为 O (1)(原地合并)。

代码缺点分析:

  1. 递归深度可能过大
    当listsSize较大时(如 1000),递归深度可达 O (logK),虽然理论上不会溢出,但相比迭代法(如优先队列)可能占用更多栈空间。
  2. 临时数组的冗余操作
    每次递归都创建临时数组temp,虽然空间复杂度为 O (K/2 + K/4 + ...) = O (K),但频繁的内存分配 / 释放可能影响性能。
    例如,当listsSize=3时,临时数组大小为 2,但实际只使用了 1 个有效元素(另一个元素未初始化)。
  3. 奇数长度处理的潜在问题
    代码中if (listsSize % 2)的判断等价于i == listsSize - 1,但更推荐直接通过i的值判断是否剩余元素,因为listsSize % 2在i未遍历完时可能不准确(虽然当前逻辑正确)。
  4. 无法处理大量空链表
    虽然代码处理了单个空链表的情况(在merge中返回非空链表),但当数组中存在大量空链表时,递归仍会遍历空指针,可优化为先过滤空链表。
相关推荐
阿蒙Amon30 分钟前
C#类型转换:从基础到进阶的全景解析
开发语言·c#
乌萨奇也要立志学C++43 分钟前
【C语言】回调函数、转移表、qsort 使用与基于qsort改造冒泡排序
c语言
猎嘤一号2 小时前
Windows11桌面解锁守护脚本
开发语言·python·opencv
蓝婷儿3 小时前
Python 数据建模与分析项目实战预备 Day 2 - 数据构建与字段解析(模拟简历结构化数据)
开发语言·python·机器学习
青衫客363 小时前
浅谈 Python 中的 yield——yield的返回值与send()的关系
开发语言·python
玩代码4 小时前
CompletableFuture 详解
java·开发语言·高并发·线程
hz_zhangrl4 小时前
CCF-GESP 等级考试 2025年6月认证C++三级真题解析
开发语言·c++·青少年编程·gesp·gesp2025年6月·c++三级
人生在勤,不索何获-白大侠5 小时前
day21——特殊文件:XML、Properties、以及日志框架
xml·java·开发语言
Dxy12393102167 小时前
Python PDFplumber详解:从入门到精通的PDF处理指南
开发语言·python·pdf
EutoCool8 小时前
Qt:布局管理器Layout
开发语言·c++·windows·嵌入式硬件·qt·前端框架