首页 > 其他分享 >23_合并K个升序链表

23_合并K个升序链表

时间:2024-10-09 10:11:31浏览次数:8  
标签:ListNode val 23 合并 next 链表 升序 节点

23_合并K个升序链表

【问题描述】

给你一个链表数组,每个链表都已经按升序排列。请你将所有链表合并到一个升序链表中,返回合并后的链表。

【算法设计思想】

使用优先队列(最小堆)

设计思想:

  1. 初始化优先队列:创建一个最小堆(优先队列),将每个链表的第一个节点加入堆中。这里堆中存储的是节点的值以及指向该节点的引用,以便于后续操作。
  2. 构建结果链表:创建一个虚拟头节点(dummy node),用于简化链表的构建过程。然后进入循环,每次从堆中取出最小的节点(即堆顶元素),将其添加到结果链表中。
  3. 更新堆:如果从堆中取出的节点有下一个节点(即该节点不是所在链表的最后一个节点),则将下一个节点加入堆中。
  4. 终止条件:当堆为空时,说明所有链表的节点都已经被处理完毕,此时结果链表构建完成。
  5. 返回结果:返回虚拟头节点的下一个节点作为合并后的链表的头节点。

这种方法的时间复杂度主要取决于堆的操作,对于k个链表,每个链表平均长度为n的情况,时间复杂度大约为O(nk log k),因为每次插入或删除堆中的元素的时间复杂度为O(log k)。

顺序合并

设计思想:

  1. 两两合并:从给定的链表列表中选取两个链表,使用一个辅助函数(如前面提到的mergeTwoLists)将这两个链表合并成一个新的已排序链表。
  2. 迭代合并:将新合并的链表与列表中的下一个链表再次进行合并。重复此过程,直到所有链表都被合并成一个链表为止。
  3. 返回结果:最终返回合并后的链表。

这种方法简单直观,但效率相对较低。对于k个链表,每次合并两个链表的时间复杂度为O(n),而需要合并k-1次,因此总的时间复杂度为O(nk^2)。这在链表数量较少时可能表现尚可,但在链表数量较多时性能会显著下降。

【算法描述】

C++(优先队列):

/**
 * 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:
    // 自定义比较器,用于最小堆
    struct Cmp {
        bool operator()(ListNode* a, ListNode* b) { 
            return a->val > b->val; // 返回 true 如果 a 的值大于 b 的值
        }
    };

    ListNode* mergeKLists(vector<ListNode*>& lists) {
        // 创建一个最小堆,使用自定义比较器 Cmp
        priority_queue<ListNode*, vector<ListNode*>, Cmp> heap;

        // 初始化最小堆,将每个链表的头节点加入堆中
        for (int i = 0; i < lists.size(); i++) {
            if (lists[i]) { // 检查链表头节点是否非空
                heap.push(lists[i]); // 将非空的头节点加入堆中
            }
        }

        // 创建一个虚拟头节点,用于简化操作
        ListNode* dummy = new ListNode(0);
        ListNode* current = dummy; // 当前指针,用于构建合并后的链表

        // 处理最小堆,直到堆为空
        while (!heap.empty()) {
            ListNode* top = heap.top(); // 取出堆顶元素(最小值节点)
            heap.pop(); // 从堆中移除堆顶元素

            current->next = top; // 将最小值节点添加到合并后的链表中
            current = current->next; // 移动当前指针

            // 如果取出的节点有下一个节点,将下一个节点加入堆中
            if (top->next) {
                heap.push(top->next);
            }
        }

        // 返回合并后的链表的头节点
        return dummy->next;
    }
};

Java(顺序合并):

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {

    /**
     * 合并两个有序链表
     * @param a 第一个链表
     * @param b 第二个链表
     * @return 合并后的链表
     */
    public ListNode mergeTwoLists(ListNode a, ListNode b) {
        // 如果其中一个链表为空,直接返回另一个链表
        if (a == null) {
            return b;
        }
        if (b == null) {
            return a;
        }

        // 创建一个虚拟头节点,方便操作
        ListNode dummy = new ListNode(0);
        ListNode current = dummy;

        // 当两个链表都不为空时,进行比较并合并
        ListNode l1 = a;
        ListNode l2 = b;
        while (l1 != null && l2 != null) {
            if (l1.val <= l2.val) {
                // 将较小的节点连接到当前节点的下一个位置
                current.next = l1;
                // 移动当前节点和 l1 指针
                current = current.next;
                l1 = l1.next;
            } else {
                // 将较小的节点连接到当前节点的下一个位置
                current.next = l2;
                // 移动当前节点和 l2 指针
                current = current.next;
                l2 = l2.next;
            }
        }

        // 如果其中一个链表已经遍历完,将另一个链表的剩余部分连接到当前节点的下一个位置
        if (l1 == null) {
            current.next = l2;
        }
        if (l2 == null) {
            current.next = l1;
        }

        // 返回合并后的链表,从虚拟头节点的下一个节点开始
        return dummy.next;
    }

    /**
     * 合并多个有序链表
     * @param lists 链表数组
     * @return 合并后的链表
     */
    public ListNode mergeKLists(ListNode[] lists) {
        // 如果链表数组为空或长度为0,直接返回null
        if (lists == null || lists.length == 0) {
            return null;
        }

        // 初始化结果链表为null
        ListNode ans = null;

        // 遍历链表数组,依次将每个链表与当前结果链表合并
        for (int i = 0; i < lists.length; i++) {
            ans = mergeTwoLists(ans, lists[i]);
        }

        // 返回最终合并后的链表
        return ans;
    }
}

C(顺序合并):

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */

/**
 * 合并两个有序链表
 * @param a 第一个链表
 * @param b 第二个链表
 * @return 合并后的链表
 */
struct ListNode* mergeTwoLists(struct ListNode* a, struct ListNode* b) {
    // 如果其中一个链表为空,直接返回另一个链表
    if (a == NULL) {
        return b;
    }

    if (b == NULL) {
        return a;
    }

    // 创建一个虚拟头节点,方便操作
    struct ListNode* dummy = (struct ListNode*)malloc(sizeof(struct ListNode));
    dummy->val = 0;
    dummy->next = NULL;
    struct ListNode* current = dummy;

    // 当两个链表都不为空时,进行比较并合并
    struct ListNode* l1 = a;
    struct ListNode* l2 = b;
    while (l1 != NULL && l2 != NULL) {
        if (l1->val <= l2->val) {
            // 将较小的节点连接到当前节点的下一个位置
            current->next = l1;
            // 移动当前节点和 l1 指针
            current = current->next;
            l1 = l1->next;
        } else {
            // 将较小的节点连接到当前节点的下一个位置
            current->next = l2;
            // 移动当前节点和 l2 指针
            current = current->next;
            l2 = l2->next;
        }
    }

    // 如果其中一个链表已经遍历完,将另一个链表的剩余部分连接到当前节点的下一个位置
    if (l1 == NULL) {
        current->next = l2;
    }

    if (l2 == NULL) {
        current->next = l1;
    }

    // 返回合并后的链表,从虚拟头节点的下一个节点开始
    struct ListNode* result = dummy->next;
    free(dummy);  // 释放虚拟头节点的内存
    return result;
}

/**
 * 合并多个有序链表
 * @param lists 链表数组
 * @param listsSize 链表数组的大小
 * @return 合并后的链表
 */
struct ListNode* mergeKLists(struct ListNode** lists, int listsSize) {
    // 如果链表数组为空或长度为0,直接返回NULL
    if (lists == NULL || listsSize == 0) {
        return NULL;
    }

    // 初始化结果链表为NULL
    struct ListNode* ans = NULL;

    // 遍历链表数组,依次将每个链表与当前结果链表合并
    for (int i = 0; i < listsSize; i++) {
        ans = mergeTwoLists(ans, lists[i]);
    }

    return ans;
}


Python(顺序合并):

# 定义单链表节点类
class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

class Solution:
    # 合并k个升序链表
    def mergeKLists(self, lists: List[Optional[ListNode]]) -> Optional[ListNode]:
        # 合并两个升序链表的辅助函数
        def mergeTwoLists(
            list1: Optional[ListNode], list2: Optional[ListNode]
        ) -> Optional[ListNode]:
            # 如果其中一个链表为空,则直接返回另一个链表
            if list1 is None:
                return list2
            if list2 is None:
                return list1
            
            # 创建一个虚拟头节点,用于简化链表操作
            dummy = ListNode(0)
            # 当前节点指针,用于构建新的合并后的链表
            current = dummy
            # 指向两个输入链表的指针
            l1 = list1
            l2 = list2

            # 遍历两个链表,直到至少有一个遍历结束
            while l1 is not None and l2 is not None:
                # 将值较小的节点连接到新链表,并移动对应的指针
                if l1.val <= l2.val:
                    current.next = l1
                    l1 = l1.next
                else:
                    current.next = l2
                    l2 = l2.next
                # 移动当前节点指针
                current = current.next

            # 将未遍历完的链表剩余部分连接到新链表
            if l1 is None:
                current.next = l2
            if l2 is None:
                current.next = l1

            # 返回合并后的新链表的头节点(跳过虚拟头节点)
            return dummy.next

        # 初始化结果链表为None
        ans = None

        # 依次将每个链表与当前结果链表合并
        for i in range(len(lists)):
            ans = mergeTwoLists(ans, lists[i])
        
        # 返回最终合并后的链表
        return ans

标签:ListNode,val,23,合并,next,链表,升序,节点
From: https://www.cnblogs.com/zeta186012/p/18453666

相关文章

  • 代码随想录算法训练营第四天|24. 两两交换链表中的节点、19.删除链表的倒数第N个节点
    24.两两交换链表中的节点力扣题目链接(opensnewwindow)给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。输入:head=[1,2,3,4]输出:[2,1,4,3]示例2:输入:head=[]输出:[]示例3:输入:head=[1]......
  • 代码随想录算法训练营第三天|链表理论基础、203.移除链表元素、707.设计链表、206.反
    链表理论基础链表的类型单链表每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。链表的入口节点称为链表的头结点也就是head。双链表单链表中的指针域只能指向节点的下一个节点。双链表:每一个节点有......
  • 7-1单链表的基本操作
    题目:7-1单链表基本操作分数20作者朱允刚单位吉林大学请编写程序实现单链表插入、删除结点等基本算法。给定一个单链表和一系列插入、删除结点的操作序列,输出实施上述操作后的链表。单链表数据域值为整数。输入格式:输入第1行为1个正整数n,表示当前单链表长度;第2行为n个......
  • 洛谷P2323 [HNOI2006] 公路修建问题
    Problem给出n个点、m条边的无向连通图,每条边具有2个边权,一高一低,我们需要选择若干条边,使得图连通的情况下选择至少k条较高边权,输出选择的边中,边权最大值的最小值,输出答案的一半(保证偶数)Slove假设每条边只具有1条边权,答案显而易见,跑一遍最小生成树即可,因为最小生成树就是最小......
  • 常见的公共 DNS 服务器地址有:谷歌 DNS:8.8.8.8 和 8.8.4.4阿里云 DNS:223.5.5.5 和 223.
    常见的公共DNS服务器地址有:谷歌DNS:8.8.8.8和8.8.4.4阿里云DNS:223.5.5.5和223.6.6.6腾讯DNS:119.29.29.29和182.254.116.116阿里公共DNS:IPv4:223.5.5.5、223.6.6.6IPv6:2400:3200::1、2400:3200:baba::1腾讯公共DNS(DNSPod):IPv4:119.29.29.29IPv6:2402:4e00::百......
  • 【RAG论文精读3】RAG论文综述1(2312.10997)-第1部分
    收录于我的专栏:AI修炼之路简介论文中英文名Retrieval-AugmentedGenerationforLargeLanguageModels:ASurvey面向大型语言模型的检索增强生成:综述论文地址arxiv地址:https://arxiv.org/abs/2312.10997精读理由这篇综述论文对RAG在大型语言模型中的应用进行了......
  • 10.8 模拟赛(2023 CSP-S 十连测 #5)
    炼石计划10月28日CSP-S十连测#5【补题】-比赛-梦熊联盟(mna.wang)复盘T1秒了。30min。T2题目越短越难。但是链的是经典题目,写了。小样例太水,大样例太大,不方便猜结论。于是先写暴力然后自己造样例。模拟了五六组感觉可以按照lca的深度降序排序,然后能选就选。这......
  • 2023 ICPC 南京
    10.5想要袋鼠。赛时5题深刻感觉到代码能力瓶颈。I签到C也是签到,需要枚举的次数很少。F似乎是签到但是队友debug卡了一百年,晚点补一下看看Gxixike秒的L思路就是贪心。我写了两遍错的,xixike重构了一下把能合并的都合并了就过了。A比较显然的是连通块里面的袋鼠都胜......
  • 最新整理-全国及各城市POI数据(2023年含七大主要城市数据)
    文章目录数据下载地址数据指标说明一、全国范围2012、2014、2016、2018、2020、2022年常用POI数据集二、全国各城市POI兴趣点数据三、2023年七大主要城市POI数据项目备注数据下载地址数据下载地址点击这里下载数据数据指标说明POI(一般作为PointofInterest的缩写......
  • 20222325 2024-2025-1 《网络与系统攻防技术》实验一实验报告
    1.实验内容缓冲区溢出基本知识:堆栈、函数调用。shellcode技术以及其在各平台的运用与防御。BOF攻击防御技术。2.实验目标本次实践的对象是一个名为pwn1的linux可执行文件。该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符串。该程序同时包含另......