Leetcode二十三题:合并K个升序链表【22/1000 python】

“合并K个升序链表”,这是一道中等难度的题目,经常出现在编程面试中。以下是该问题的详细描述、解题步骤、不同算法的比较、代码示例及其分析。

问题描述

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

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

示例:

输入: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

方法一:直接合并

解题步骤

  1. 初始化:
    • 如果链表数组为空,则返回None。
    • 如果链表数组中只有一个链表,则直接返回这个链表。
  2. 逐对合并链表:
    • 初始化merged_list为lists[0],即从第一个链表开始。
    • 逐个遍历余下的链表,与merged_list进行合并,每次合并后更新merged_list。
  3. 合并两个链表的函数:
    • 创建一个哑结点dummy作为合并链表的起始节点。
    • 使用两个指针分别指向两个链表的头部,比较指针所指节点的值,将较小值节点连接到结果链表上,然后移动该指针到下一个节点。
    • 如果某一链表遍历完毕,将另一链表的剩余部分直接连接到结果链表的尾部。
    • 返回哑结点的下一个节点,即合并后链表的头部。
  4. 完成合并:
    • 继续遍历并合并剩余的链表,直至所有链表均合并完成。

代码示例

class ListNode:def __init__(self, val=0, next=None):self.val = valself.next = nextdef mergeTwoLists(l1: ListNode, l2: ListNode) -> ListNode:dummy = ListNode(0)current = dummywhile l1 and l2:if l1.val < l2.val:current.next = l1l1 = l1.nextelse:current.next = l2l2 = l2.nextcurrent = current.next# Attach the remaining part of l1 or l2current.next = l1 if l1 is not None else l2return dummy.nextdef mergeKLists(lists: List[Optional[ListNode]]) -> Optional[ListNode]:if not lists:return Noneif len(lists) == 1:return lists[0]merged_list = lists[0]for i in range(1, len(lists)):merged_list = mergeTwoLists(merged_list, lists[i])return merged_list

性能分析

时间复杂度:对于k个链表的情况,直接合并的时间复杂度是O(kN),其中N是链表中的节点总数。这是因为每次合并操作需要遍历涉及的两个链表的长度,而链表长度随着合并次数增加而增长。
空间复杂度:O(1),不计入输入和输出占用的空间,合并过程中只使用了常数额外空间。

结论

直接合并是一个简单直观的方法,适合链表数量较少或对时间复杂度要求不是非常严格的情况。然而,对于大量链表的合并,使用最小堆或分治法(如两两合并)可能会更高效。

方法二:使用最小堆

解题步骤

  1. 初始化最小堆
    • 创建一个空的最小堆(优先队列)来存储链表节点,堆中的元素按节点的值排序。
    • 遍历所有链表,将每个链表的头节点加入最小堆中。
  2. 构建结果链表
    • 创建一个哑结点(dummy node)作为结果链表的头部,这样可以方便地添加新节点。
    • 使用一个指针current跟踪结果链表的最后一个节点。
  3. 遍历并合并
    • 当最小堆不为空时,执行以下操作:
    • 从堆中弹出最小元素(当前最小节点)。
    • 将current的next指针指向这个最小节点。
    • 移动current指针到最小节点。
    • 如果这个最小节点有后继节点,则将后继节点加入最小堆中。
  4. 完成合并
    • 当最小堆为空时,所有链表的节点都已链接到结果链表中。
    • 返回哑结点的next,即合并后链表的头部。

代码示例

import heapqclass ListNode:def __init__(self, val=0, next=None):self.val = valself.next = nextdef mergeKLists(lists):if not lists:return None# Create a heap and a dummy node to start the merged listheap = []dummy = ListNode(0)current = dummy# Initial population of the heap with the first node of each list, if availablefor i, node in enumerate(lists):if node:heapq.heappush(heap, (node.val, i, node))# Iterate over the heap and build the merged listwhile heap:val, idx, node = heapq.heappop(heap)current.next = nodecurrent = current.nextif node.next:heapq.heappush(heap, (node.next.val, idx, node.next))return dummy.next

性能分析

时间复杂度:每个节点被处理一次,而且每次处理涉及的时间复杂度为O(log k),因此总的时间复杂度是O(Nlogk),其中是所有链表中元素的总数,是链表的数量。
空间复杂度:最小堆中最多存储个元素,因此空间复杂度是O(k)。

结论

使用最小堆的方法在合并多个链表时非常有效,尤其是当链表数量较多时。这种方法的时间复杂度相对较低,是因为它能快速地找到当前最小的节点并将其加入到结果链表中,而空间复杂度则主要由堆的大小决定。这使得最小堆方法在处理大规模数据时表现出色。

方法三:分治合并

解题步骤

  1. 递归分治函数定义
    • 创建一个函数mergeKLists,如果列表为空或长度为1,直接返回。
    • 如果列表长度大于1,将链表列表分成两半,分别对这两半递归调用mergeKLists。
  2. 合并两个链表的函数
    • 创建另一个辅助函数mergeTwoLists用于合并两个链表。这个函数将两个链表头作为输入,合并后返回新链表的头。
  3. 执行分治合并
    • 在mergeKLists中,通过递归地将链表数组拆分至只剩单个链表,然后开始合并。
    • 使用mergeTwoLists逐对合并链表,直至整个数组合并为一个链表。
  4. 返回结果
    • 递归完全执行完毕后,返回合并后的链表头部。

代码示例

class ListNode:def __init__(self, val=0, next=None):self.val = valself.next = nextdef mergeTwoLists(l1: ListNode, l2: ListNode) -> ListNode:if not l1 or not l2:return l1 or l2dummy = ListNode(0)current = dummywhile l1 and l2:if l1.val < l2.val:current.next = l1l1 = l1.nextelse:current.next = l2l2 = l2.nextcurrent = current.nextcurrent.next = l1 or l2return dummy.nextdef mergeKLists(lists: List[ListNode]) -> ListNode:if not lists:return Noneif len(lists) == 1:return lists[0]mid = len(lists) // 2left = mergeKLists(lists[:mid])right = mergeKLists(lists[mid:])return mergeTwoLists(left, right)

性能分析

时间复杂度
• 每次合并操作需要线性时间,即O(n),其中n是参与合并的两个链表的总节点数。
• 通过每次递归减半链表数组的长度,整体上每层递归需要O(N)时间,其中N是所有链表中元素的总数。
• 递归的深度是O(log k),因此总的时间复杂度是O(N logk)。
空间复杂度
• 递归调用栈的深度为O(logk),因此空间复杂度为O(logk)。

结论

分治合并是解决合并多个链表的问题中非常有效的方法,尤其适合处理大量链表的情况。它的时间复杂度与使用最小堆的方法相同,但通常在实际应用中由于常数因子较小而表现更优。此外,分治法的代码结构清晰,易于理解和实现,使其成为面试和实际工程中的常用策略。

总结

在这里插入图片描述
合并 K 个升序链表可以通过多种算法实现,包括直接合并、使用最小堆和分治合并。在面试中,根据具体情况选择最适合的方法。其中,使用最小堆和分治合并的方法因其较优的时间复杂度通常更受青睐。这些方法不仅展示了数据结构的有效使用,也体现了分治策略在实际问题中的应用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/815905.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

浏览器密码框明文密文兼容edge的问题

在网页中注册会员的时候&#xff0c;经常需要输入用户名&#xff08;账号&#xff09;和密码&#xff0c;在输入密码的时候&#xff0c;为了防止用户输错密码&#xff0c;经常会给密码框加一个小功能&#xff0c;就是点击密码框右侧闭着的小眼睛&#xff0c;可以让密文变成明文…

【教学类-51-01】20240411动物皮毛图片的彩色打印PDF制作(一页两张图片,2个表格)

作品展示 背景需求&#xff1a; 为了便于快速做出A4两份图片的效果&#xff0c;设计以下代码&#xff0c;进行图片的PDF合成打印 代码参考&#xff1a; 【教学类-50-06】20240410“数一数”4类星号图片制作PDF学具-CSDN博客文章浏览阅读531次&#xff0c;点赞8次&#xff0c;收…

医院预约系统微信小程序APP前后端

医院预约系统具体功能介绍&#xff1a;展示信息、可以注册和登录&#xff0c; 预约&#xff08;包含各个科室的预约&#xff0c;可以预约每个各个医生&#xff09;&#xff0c;就诊引导包含预约的具体信息&#xff0c;包含就诊时间、就诊科室、就诊医生以及就诊人信息、和支付状…

基于”Python+”多技术融合在蒸散发与植被总初级生产力估算中的应用

熟悉蒸散发ET及其组分&#xff08;植被蒸腾Ec、土壤蒸发Es、冠层截留Ei&#xff09;、植被总初级生产力GPP的概念和碳水耦合的基本原理&#xff1b;掌握利用Python与ArcGIS工具进行课程相关的操作&#xff1b;熟练掌握国际上流行的Penman-Monteith模型&#xff0c;并能够应用该…

有真的副业推荐吗?

#有真的副业推荐吗# 我做副业项目的时候&#xff0c;认识了一位带娃宝妈&#xff0c;讲一下她空闲时间做副业赚钱的故事吧。在一个温馨的小家庭里&#xff0c;李婷是一位全职宝妈&#xff0c;她的主要任务是照顾和陪伴自己可爱的宝宝。然而&#xff0c;随着宝宝逐渐长大&#x…

Mysql的事务隔离级别以及事务的四大特性。

MySQL 的事务隔离级别是数据库管理系统中的一个重要概念&#xff0c;它决定了事务如何隔离和影响其他并发事务。MySQL 支持四种事务隔离级别&#xff0c;分别是&#xff1a;读未提交&#xff08;READ UNCOMMITTED&#xff09;、读已提交&#xff08;READ COMMITTED&#xff09;…

【MATLAB源码-第188期】基于matlab的64QAM系统相位偏移估计EOS算法仿真,对比补偿前后的星座图误码率。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 1. 引言 M-QAM调制技术的重要性 现代通信系统追求的是更高的数据传输速率和更有效的频谱利用率。M-QAM调制技术&#xff0c;作为一种高效的调制方案&#xff0c;能够通过在相同的带宽条件下传输更多的数据位来满足这一需求…

Ubuntu使用SSH的X11Forwarding

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、X11Forwarding是什么&#xff1f;二、使用步骤1.打开SSH配置2.MobaXterm 三、测试总结 前言 有没有那么一种需求&#xff0c;就是有时候你需要将远端的画面…

dbeaver数据库语言编辑器设置jdbc驱动

打开 dbeaver 软件 数据库 -> 驱动管理器 以mysql为例 双击 MySQL -> 库 -> 添加工件 然后 打开maven组件库 官网 找到mysql驱动对应的maven工件地址 复制进去然后确认就行了 参考 大神博客

VM虚拟机安装Linux系统Redhat7.4版本

1、打开VM软件创建一个新的虚拟机&#xff1a; 可选择经典安装&#xff0c;也可以选择自定义安装&#xff0c;本次选择自定义安装&#xff0c;然后选择下一步 2、直接默认选择下一步即可 3、选择稍后安装操作系统&#xff0c;选择下一步 4、之后选择呢需要安装客户机的操作系统…

Linux 添加启动服务--Service

1&#xff0c;服务配置service文件 Service 服务的实际作用是开启后自动启动服务&#xff0c;运行一些不须要登录的程序&#xff0c;任务。 实例1、上电自动连接WIFI热点 1.1 新建.service文件 /etc/systemd/system/wificonnect.service [Unit] DescriptionService [wifico…

策略为王股票软件源代码\StkUI\View\BaseView.cpp-------显示股票基本资料的视图-------程序代码都在里面了

CString strHeader info.GetStockCode(); strHeader " "; /修改 strHeader info.GetStockName(); strHeader "\r\n\r\n "; GetEditCtrl().SetWindowText( strHeader ); GetEditCtrl().SetSel…

Linux配置程序后台运行(前后台来回切换)

Linux配置程序后台运行 在日常开发过程中&#xff0c;会遇到我们在前台运行程序&#xff0c;此时我们临时有事&#xff0c;但不能关闭终端&#xff0c;否则程序就会在电脑熄屏&#xff08;终端session断开后&#xff09;停止运行。 那么作为一个合格的开发&#xff0c;就必须要…

ELFK日志分析系统之搭建ELF+Filebeaat+Zookeeper+Kafka

引言 结合前面所学 http://ELK日志分析系统 一、为什么要做日志分析平台 随着业务量的增长&#xff0c;每天业务服务器将会产生上亿条的日志&#xff0c;单个日志文件达几个GB&#xff0c;这时我们发现用Linux自带工具&#xff0c;cat grep awk 分析越来越力不从心了&#…

【Linux】序列化与反序列化{服客编程/守护进程/JSON}

文章目录 1.引入2. 静态成员函数3.TCP&#xff1a;传输控制协议4.守护进程4.0前台进程4.1介绍4.2认识4.3会话4.3ps axj4.4理解4.5/dev/null4.6守护进程和孤儿进程 5.JSON6.完整代码6.1Makefile6.2Socket.hpp6.3Protocol.hpp6.4Log.hpp6.5Daemon.hpp6.6TcpServer.hpp6.7Client.c…

Spring高手之路17——动态代理的艺术与实践

文章目录 1. 背景2. JDK动态代理2.1 定义和演示2.2 不同方法分别代理2.3 熔断限流和日志监控 3. CGLIB动态代理3.1 定义和演示3.2 不同方法分别代理&#xff08;对比JDK动态代理写法&#xff09;3.3 熔断限流和日志监控&#xff08;对比JDK动态代理写法&#xff09; 4. 动态代理…

基于ssm的智慧餐厅点餐管理系统设计与实现(java项目+文档+元)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于ssm的智慧餐厅点餐管理系统。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 智慧餐厅点餐管理系统设计…

踩了一堆坑,终于掌握了postgreSQL主从流的精髓

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是【IT邦德】&#xff0c;江湖人称jeames007&#xff0c;10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】&#xff01;&#x1f61c;&am…

【数据结构】4.List的介绍

目录 1.什么是List 2.常见接口介绍 3.List的使用 1.什么是List 在集合框架中&#xff0c;List是一个接口&#xff0c;继承自Collection。 Collection也是一个接口&#xff0c;该接口中规范了后序容器中常用的一些方法&#xff0c;具体如下&#xff1a; Iterable也是一个接口…

syscall的检测与绕过(下)

syscall的检测与绕过 ntdll中syscall被执行的格式大致 我们可以通过检测mov r10, rcx类似的代码来确定程序是否直接进行系统调用。 但是很容易被bypass 而且还可以写出很多不一样的写法&#xff0c;显然这个方式是不行的。很轻易就会被bypass。 当然也可以检测syscall指令&a…