算法札记——5.15

今日又是记录一道链表归并的题目------23. 合并 K 个升序链表 - 力扣(LeetCode)

首先可以想到的一种简单的思路,便是用一个哨兵位的头节点将数组的首位个链表连接寄来,然后依次遍历首链表之后每个链表,每次遍历将各个链表进行合并到哨兵位指向的链表:

cpp 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        int n = lists.size();
        if (n == 0)
            return nullptr;
        ListNode t(-1, lists[0]);
        ListNode* phead = &t;
        for (int i = 1; i < n; ++i)
        {
            ListNode* prev = phead, *l1 = prev->next, *l2 = lists[i];
            while (l1 && l2)
            {
                if (l1->val <= l2->val)
                {
                    prev->next = l1;
                    l1 = l1->next;
                }
                else
                {
                    prev->next = l2;
                    l2 = l2->next;
                }
                prev = prev->next;
            }
            if (l1)
                prev->next = l1;
            if (l2)
                prev->next = l2;
        }
        return phead->next;
    }
};

但是显然该解法的时间复杂度是O(N * K) 不够优雅。是否存在时间复杂度更低的解法。有的,此处可以使用归并排序的方法解决,此时会存在一个问题:此处数组存储的是一个个的链表,在归并的过程中,会将两个链表合并为一个,也就是说数组中会出现空缺的位置。所以就需要约定将合并完成的链表放到一个固定的位置,让下次归并时可以直接找到该链表,这里约定每次合并好之后将·链表放到左区间的首位置(也就是单趟归并的整个区间的首位)。

以下是代码实现:

cpp 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
    void MergeSortLists(vector<ListNode*>& lists, int begin, int end)
    {
        if (begin >= end)
            return;
        
        int mid = begin + (end - begin) / 2;
        int left1 = begin, left2 = mid + 1;

        MergeSortLists(lists, left1, mid);
        MergeSortLists(lists, left2, end);

        ListNode t(-1, lists[begin]);
        ListNode* phead = &t, *prev = phead;
        ListNode *l1 = lists[left1], *l2 = lists[left2];
        while (l1 && l2)
        {
            if (l1->val <= l2->val)
            {
                prev->next = l1;
                l1 = l1->next;
            }
            else
            {
                prev->next = l2;
                l2 = l2->next;
            }
            prev = prev->next;
        }
        if (l1)
            prev->next = l1;
        if (l2)
            prev->next = l2;
        //防止前一个链表为空的情况,直接将合并完成的链表放到数组中
        lists[begin] = phead->next;
    }
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        int n = lists.size();
        if (n == 0)
            return nullptr;
        MergeSortLists(lists, 0, n - 1);
        return lists[0];
    }
};

虽然时间复杂度降到了O(NlogK), 但是上述解法同时存在O(logN) 的函数栈帧的开销,因此可以使用非递归版本:

cpp 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        int n = lists.size();
        if (n == 0)
            return nullptr;
        int gap = 1;
        while (gap < n)
        {
            for (int i = 0; i < n; i += 2 * gap)
            {
                int begin = i, end = min(i + 2 * gap, n) - 1;

                int left1 = begin, left2 = i + gap;
                if (left2 > end)
                    break;

                ListNode t(-1, lists[begin]);
                ListNode* phead = &t, *prev = phead;
                ListNode *l1 = lists[left1], *l2 = lists[left2];
                while (l1 && l2)
                {
                    if (l1->val <= l2->val)
                    {
                        prev->next = l1;
                        l1 = l1->next;
                    }
                    else
                    {
                        prev->next = l2;
                        l2 = l2->next;
                    }
                    prev = prev->next;
                }
                if (l1)
                    prev->next = l1;
                if (l2)
                    prev->next = l2;
                //防止前一个链表为空的情况,直接将合并完成的链表放到数组中
                lists[begin] = phead->next;
            }
            gap *= 2;
        }
        return lists[0];
    }
};
相关推荐
鱼子星_1 小时前
【数据结构与算法】OJ题目详解(一)-单链表:从易到难的面试OJ题目
c语言·数据结构·算法·链表·面试·职场和发展
人道领域1 小时前
【LeetCode刷题日记】递归与回溯实战 257.二叉树的所有路径——一篇文章彻底搞懂回溯
开发语言·python·算法·leetcode
ulias2121 小时前
leetcode热题 - 7
数据结构·算法·leetcode
吃好睡好便好1 小时前
在Matlab中用sphere( )函数绘制球面图
开发语言·前端·javascript·学习·算法·matlab·信息可视化
图码1 小时前
矩阵中的“对角线强迫症”:如何优雅地判断Toeplitz矩阵?
数据结构·c++·线性代数·算法·青少年编程·矩阵
lynnlovemin1 小时前
二分查找与二分答案算法详解(基于C++实现)
c语言·开发语言·算法·二分查找·二分答案
瑞行AI1 小时前
一套数据格式框架搞定大模型微调和对齐训练
算法·语言模型
玛卡巴卡ldf1 小时前
【LeetCode 手撕算法】(动态规划)爬楼梯、杨辉三角、打家劫舍、完全平方数、零钱兑换、单词拆分、最长递增子序列、乘积最大子数组、分割等和子集
java·数据结构·算法·leetcode·动态规划·力扣
jake·tang1 小时前
深度解析 VESC 参数辨识源码:电阻、电感与磁链
arm开发·c++·嵌入式硬件·算法·数学建模·傅立叶分析