Java实现K个排序链表的高效合并:逐一合并、分治法与优先队列详解
Java实现K个排序链表的高效合并:逐一合并、分治法与优先队列详解
在算法和数据结构的学习中,链表是一个非常基础但又极具挑战的数据结构。尤其是当面对合并多个排序链表的问题时,如何在保证效率的前提下实现代码的简洁与高效,往往是算法设计的关键。本文将详细探讨如何使用Java中的优先队列(PriorityQueue)来实现K个排序链表的高效合并,并为常用的解决方案提供示例代码。
1. 问题背景
在这个问题中,我们有K个已经排序的链表,目标是将这些链表合并成一个新的排序链表,并返回合并后的链表头节点。简单的例子如下:
- 链表1:1 -> 4 -> 5
- 链表2:1 -> 3 -> 4
- 链表3:2 -> 6
合并后的链表为:1 -> 1 -> 2 -> 3 -> 4 -> 4 -> 5 -> 6
这个问题的难点在于如何高效地处理K个链表的合并。以下是三种常用的解决方案。
2. 常用解决方案
- 逐一合并法
逐一合并法的思路是依次合并两个链表,最终得到结果链表。虽然这种方法简单易懂,但其时间复杂度较高,尤其是在链表数量较多的情况下。
示例代码:
class ListNode {int val;ListNode next;ListNode() {}ListNode(int val) { this.val = val; }ListNode(int val, ListNode next) { this.val = val; this.next = next; }
}public class MergeKListsSequentially {public ListNode mergeTwoLists(ListNode l1, ListNode l2) {ListNode dummy = new ListNode(0);ListNode tail = dummy;while (l1 != null && l2 != null) {if (l1.val < l2.val) {tail.next = l1;l1 = l1.next;} else {tail.next = l2;l2 = l2.next;}tail = tail.next;}tail.next = (l1 != null) ? l1 : l2;return dummy.next;}public ListNode mergeKLists(ListNode[] lists) {if (lists == null || lists.length == 0) {return null;}ListNode mergedList = lists[0];for (int i = 1; i < lists.length; i++) {mergedList = mergeTwoLists(mergedList, lists[i]);}return mergedList;}public static void main(String[] args) {ListNode list1 = new ListNode(1, new ListNode(4, new ListNode(5)));ListNode list2 = new ListNode(1, new ListNode(3, new ListNode(4)));ListNode list3 = new ListNode(2, new ListNode(6));ListNode[] lists = new ListNode[]{list1, list2, list3};MergeKListsSequentially solution = new MergeKListsSequentially();ListNode mergedList = solution.mergeKLists(lists);while (mergedList != null) {System.out.print(mergedList.val + " ");mergedList = mergedList.next;}}
}
解释:
- 通过
mergeTwoLists方法,将两个有序链表合并成一个新的有序链表。 - 在
mergeKLists方法中,通过循环依次合并所有链表。
时间复杂度:
最坏情况下为O(KN),其中K是链表的数量,N是每个链表的平均长度。
- 分治法
分治法是一种更高效的解决方案,通过递归地将链表分成两部分,分别进行合并,最终得到完整的合并链表。
示例代码:
class ListNode {int val;ListNode next;ListNode() {}ListNode(int val) { this.val = val; }ListNode(int val, ListNode next) { this.val = val; this.next = next; }
}public class MergeKListsDivideAndConquer {public ListNode mergeTwoLists(ListNode l1, ListNode l2) {ListNode dummy = new ListNode(0);ListNode tail = dummy;while (l1 != null && l2 != null) {if (l1.val < l2.val) {tail.next = l1;l1 = l1.next;} else {tail.next = l2;l2 = l2.next;}tail = tail.next;}tail.next = (l1 != null) ? l1 : l2;return dummy.next;}public ListNode mergeKLists(ListNode[] lists, int start, int end) {if (start == end) {return lists[start];}int mid = start + (end - start) / 2;ListNode left = mergeKLists(lists, start, mid);ListNode right = mergeKLists(lists, mid + 1, end);return mergeTwoLists(left, right);}public ListNode mergeKLists(ListNode[] lists) {if (lists == null || lists.length == 0) {return null;}return mergeKLists(lists, 0, lists.length - 1);}public static void main(String[] args) {ListNode list1 = new ListNode(1, new ListNode(4, new ListNode(5)));ListNode list2 = new ListNode(1, new ListNode(3, new ListNode(4)));ListNode list3 = new ListNode(2, new ListNode(6));ListNode[] lists = new ListNode[]{list1, list2, list3};MergeKListsDivideAndConquer solution = new MergeKListsDivideAndConquer();ListNode mergedList = solution.mergeKLists(lists);while (mergedList != null) {System.out.print(mergedList.val + " ");mergedList = mergedList.next;}}
}
解释:
mergeKLists方法通过分治的方式递归地将链表两两合并,最终得到合并的结果链表。
时间复杂度:
时间复杂度为O(N log K),其中N是所有节点的总数,K是链表的数量。
- 优先队列法
使用优先队列可以更加高效地解决问题。优先队列可以自动维护一个有序的队列,确保每次取出最小的节点,进行合并。
示例代码:
import java.util.PriorityQueue;class ListNode {int val;ListNode next;ListNode() {}ListNode(int val) { this.val = val; }ListNode(int val, ListNode next) { this.val = val; this.next = next; }
}public class MergeKListsUsingPriorityQueue {public ListNode mergeKLists(ListNode[] lists) {PriorityQueue<ListNode> queue = new PriorityQueue<>((o1, o2) -> o1.val - o2.val);// 将每个链表的头节点加入优先队列for (ListNode list : lists) {if (list != null) {queue.add(list);}}// 创建虚拟头节点ListNode dummy = new ListNode(0);ListNode tail = dummy;// 逐步处理优先队列中的节点while (!queue.isEmpty()) {ListNode minNode = queue.poll();tail.next = minNode;tail = tail.next;// 如果最小节点的下一个节点不为空,继续加入优先队列if (minNode.next != null) {queue.add(minNode.next);}}// 防止链表成环,尾节点指向nulltail.next = null;return dummy.next;}public static void main(String[] args) {ListNode list1 = new ListNode(1, new ListNode(4, new ListNode(5)));ListNode list2 = new ListNode(1, new ListNode(3, new ListNode(4)));ListNode list3 = new ListNode(2, new ListNode(6));ListNode[] lists = new ListNode[]{list1, list2, list3};MergeKListsUsingPriorityQueue solution = new MergeKListsUsingPriorityQueue();ListNode mergedList = solution.mergeKLists(lists);while (mergedList != null) {System.out.print(mergedList.val + " ");mergedList = mergedList.next;}}
}
解释:
mergeKLists方法使用优先队列存储每个链表的头节点,并逐步处理优先队列中的最小节点,确保合并后的链表依然有序。
时间复杂度:
时间复杂度为O(N log K),其中N是所有节点的总数,K是链表的数量。
4. 优化
将每个链表的头节点插入优先队列,并通过逐步处理队列中的最小节点来维护链表的顺序。每次从队列中取出一个节点后,我们将该节点的下一个节点(如果存在)加入队列。这样可以减少优先队列的操作次数,从而提高效率。
示例代码:
import java.util.PriorityQueue;class MergeKSortedListsOptimized {public ListNode mergeKLists1(ListNode[] lists) {// 创建一个最小堆优先队列,根据节点的值进行排序PriorityQueue<ListNode> queue = new PriorityQueue<>((o1, o2) -> o1.val - o2.val);// 遍历所有链表for (ListNode list : lists) {// 如果链表不为空if (list != null) {// 将链表头节点加入优先队列queue.add(list);}}// 创建一个虚拟头节点ListNode dummy = new ListNode(0);// 尾节点指向虚拟头节点ListNode tail = dummy;// 当优先队列不为空时while (!queue.isEmpty()) {// 从优先队列中取出最小的节点ListNode minNode = queue.poll();// 将最小节点连接到当前链表的末尾tail.next = minNode;// 更新尾节点为当前最小节点tail = tail.next;// 如果最小节点还有下一个节点if (minNode.next != null) {// 将最小节点的下一个节点加入优先队列queue.add(minNode.next);}}// 将尾节点的next指针置为null,表示链表结束tail.next = null;// 返回合并后的链表的头节点(虚拟头节点的下一个节点)return dummy.next;}
}
解释:
该实现与前一个方法类似,但不同的是我们只将链表的头节点加入优先队列,然后逐个处理节点。在处理每个节点时,如果它有下一个节点,我们将下一个节点加入队列。这样做可以有效减少优先队列的操作次数,提高算法效率。
时间复杂度:
时间复杂度为 O(N * log(K)),其中 K 是链表的数量,N 是所有链表中节点的总数。
4. 总结
通过以上三种常用的解决方案,我们可以清楚地看到,不同的方法在时间复杂度和实现复杂度上的权衡。对于链表数量较少的情况,逐一合并法可能更简单易行;而在链表数量较多时,分治法和优先队列法则可以更高效地解决问题。在实际开发中,优先队列法常常是首选,因为它在效率和实现上都表现得十分优秀。
相关文章:
Java实现K个排序链表的高效合并:逐一合并、分治法与优先队列详解
Java实现K个排序链表的高效合并:逐一合并、分治法与优先队列详解 在算法和数据结构的学习中,链表是一个非常基础但又极具挑战的数据结构。尤其是当面对合并多个排序链表的问题时,如何在保证效率的前提下实现代码的简洁与高效,往往…...
Xinstall揭秘:高效App推广背后的黑科技
在移动互联网时代,App的运营推广成为了开发者们最为关注的话题之一。然而,随着市场竞争的加剧,推广难度也越来越大。这时候,一款名为Xinstall的品牌走进了我们的视线,它以其独特的技术和解决方案,为App推广…...
星巴克VS瑞幸,新王、旧王之争给新CEO带来哪些启示
“变化是生命的元素,求变是生命的力量”,马克吐温曾这样解释生命。而商场上何尝不是如此,正因为世异则事异,求变也是企业的常态。 近日,星巴克官宣布莱恩尼科尔(Brian Niccol)将于9月9日接替拉什曼纳拉辛汉(Laxman Na…...
C语言 | Leetcode C语言题解之第354题俄罗斯套娃信封问题
题目: 题解: int cmp(int** a, int** b) {return (*a)[0] (*b)[0] ? (*b)[1] - (*a)[1] : (*a)[0] - (*b)[0]; }int maxEnvelopes(int** envelopes, int envelopesSize, int* envelopesColSize) {if (envelopesSize 0) {return 0;}qsort(envelopes, …...
大型俄罗斯国际展览会介绍
今天分享一些在俄罗斯比较知名的国际展览会,以下展会大多为一年一届,具体展出时间大家可去网上查询了解,充分利用合适的展会,无疑是企业拓展市场、塑造品牌、对接资源、紧跟行业趋势的重要途径。 1、俄罗斯国际食品展览会 展览地…...
CST软件仿真案例:圆极化平板天线仿真02
本期继续完成一款圆极化Patch天线的仿真实例。读者可以完整的了解到怎么用CST微波工作室,完成对一款天线建模、设置到仿真分析的完整过程。 本期中,我们要设计的圆极化天线尺寸如下图所示: 本期内容是接着上期部分开始。首先先完成仿真实例0…...
【前端】vue监视属性和计算属性对比
首先分开讲解各个属性的作用。 1.计算属性 作用:用来计算出来一个值,这个值调用的时候不需要加括号,会根据依赖进行缓存,依赖不变,computed的值不会重新计算。 const vm new Vue({el:#root,data:{lastName:张,firstNa…...
探索提示工程 Prompt Engineering的奥妙
一、探索提示工程 Prompt Engineering 1. 介绍通用人工智能和专用人工智能 人工智能(AI)可以分为通用人工智能(AGI)和专用人工智能(Narrow AI)。AGI是一种能够理解、学习和执行任何人类可以完成的任务的智…...
算法阶段总结1
阶段总结 通过今天晚上的这场div2我深刻的意识到,光是会找窍门是远远不够的,你得会基础的建图,dp,高级数据结构,你这样才可以不断的提升自己,不可以一直在一个阶段停留下去,构造题可以刷下去&a…...
前端宝典之七:React性能优化实战精华篇
本文主要讲解实战项目中React性能优化的方法,主要分为三个大的方面:减少不必要的组件更新、组件优化以及tree-shaking,共11个方法 一、减少不必要组件更新 以下是一些可以避免在 React 提交阶段进行不必要重新渲染的方法: 1、使…...
【Dash】feffery_antd_components 简单入门示例
一、简单了解 feffery_antd_components 简称 fac ,是一个基于 Ant Design 的 Dash 第三方组件,由Feffery 老师开源维护的 Python 网页开发组件库,它具有丰富的页面常用交互组件功能,使开发者可以使用纯Python的方式快速构建现代…...
JAVA学习-练习试用Java实现“路径交叉”
问题: 给定一个整数数组 distance 。从 X-Y 平面上的点 (0,0) 开始,先向北移动 distance[0] 米,然后向西移动 distance[1] 米,向南移动 distance[2] 米,向东移动 distance[3] 米,持续移动。也就是说&#…...
element组件封装
1.上传组件 <!--文件上传组件--> <template><div class"upload-file"><el-uploadref"fileUpload"v-if"props.type default":action"baseURL other.adaptationUrl(props.uploadFileUrl)":before-upload"h…...
Mysql (面试篇)
目录 唯一索引比普通索引快吗 MySQL由哪些部分组成,分别用来做什么 MySQL查询缓存有什么弊端,应该什么情况下使用,8.0版本对查询缓存由上面变更 MyISAM和InnoDB的区别有哪些 MySQL怎么恢复半个月前的数据 MySQL事务的隔离级别ÿ…...
【python】深入探讨python中的抽象类,创建、实现方法以及应用实战
✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,…...
微前端传值
在微前端架构中,不同子应用之间通过 postMessage 进行通信是一种常见的做法。这种方式允许不同源的窗口之间进行安全的信息交换。 下面是如何使用 postMessage 在微前端环境中发送和接收消息的示例。 步骤 1: 发送消息 假设您有一个主应用(host app&a…...
《学会 SpringBoot · 依赖管理机制》
📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗 🌻 CSDN入驻不久,希望大家多多支持,后续会继续提升文章质量,绝不滥竽充数…...
全网行为管理软件有哪些?5款总有一款适合你的企业!
如今企业越来越依赖互联网进行日常运营和业务发展,网络行为管理变得日益重要。 为了确保网络安全、提高员工工作效率、避免敏感信息外泄等问题,企业往往需要借助全网行为管理软件来监控和管理内部网络的使用情况。 本文将为您介绍五款热门的全网行为管理…...
以简单的例子从头开始建spring boot web多模块项目(二)-mybatis简单集成
继续使用以简单的例子从头开始建spring boot web多模块项目(一)中的项目进行mybatis集成。 1、pom.xml文件中,增加相关的依赖包的引入,分别是mybatis-spring-boot-starter、lombok、mysql-connector-java 如下: <d…...
Golang | Leetcode Golang题解之第354题俄罗斯套娃信封问题
题目: 题解: func maxEnvelopes(envelopes [][]int) int {n : len(envelopes)if n 0 {return 0}sort.Slice(envelopes, func(i, j int) bool {a, b : envelopes[i], envelopes[j]return a[0] < b[0] || a[0] b[0] && a[1] > b[1]})f : …...
ESP32/ESP8266轻量级HA MQTT自动发现C++库
1. 项目概述 HA MQTT Discovery 是一个专为嵌入式平台(特别是 ESP32/ESP8266)设计的轻量级 C 库,用于实现与 Home Assistant 的原生 MQTT 自动发现(Auto-Discovery)协议兼容的设备与实体注册。其核心目标并非替代完整…...
别再手动处理工单了!手把手教你用Docker Compose一键部署Ferry工单系统(附避坑指南)
容器化部署Ferry工单系统:10分钟打造高可用生产环境 传统工单系统部署往往需要耗费数小时在环境配置和依赖安装上,而Docker Compose的出现彻底改变了这一局面。想象一下,当你接手一个新项目需要快速搭建工单系统时,不再需要逐行执…...
猫抓插件:突破网页资源限制的媒体捕获解决方案
猫抓插件:突破网页资源限制的媒体捕获解决方案 【免费下载链接】cat-catch 猫抓 chrome资源嗅探扩展 项目地址: https://gitcode.com/GitHub_Trending/ca/cat-catch 在数字内容爆炸的时代,我们每天浏览的网页中蕴含着丰富的视频、音频和图片资源。…...
从零理解自然数系统:用Python类模拟皮亚诺公理(含加法乘法实现)
从零构建自然数系统:用Python类实现皮亚诺公理与算术运算 在计算机科学中,自然数系统的构建是一个令人着迷的基础课题。当我们抛开编程语言内置的数字类型,仅用最基本的类和递归概念来重新定义自然数时,会惊讶地发现数学的抽象之美…...
不用第三方工具!用Altium Designer 24原生功能实现Allegro到PADS的PCB文件转换
解锁Altium Designer 24原生转换能力:Allegro到PADS的PCB文件高效迁移指南 在硬件开发领域,跨EDA平台协作已成为常态。当设计团队使用不同工具链时,文件格式转换往往成为效率瓶颈。传统方案依赖第三方转换工具,不仅增加成本&#…...
无需代码!用Qwen2.5-VL-7B-Instruct实现智能图片分析与物体检测
无需代码!用Qwen2.5-VL-7B-Instruct实现智能图片分析与物体检测 你是不是也遇到过这样的场景:手头有一堆图片,需要快速提取里面的文字、识别物体、或者描述图片内容?传统方法要么需要写代码调用API,要么得安装复杂的软…...
ES10(ES2019)新特性完整指南
ES10(ES2019)新特性发布时间:2019年6月 ES10 新增了数组扁平化、对象转换、字符串修剪等实用方法。1. Array.prototype.flat() 将嵌套数组"拉平",返回一个新数组: 基本用法 [1, 2, [3, 4]].flat(); //…...
CPUDoc:解锁CPU隐藏性能的智能优化工具
CPUDoc:解锁CPU隐藏性能的智能优化工具 【免费下载链接】CPUDoc 项目地址: https://gitcode.com/gh_mirrors/cp/CPUDoc 在当今计算环境中,CPU性能优化已成为提升整体系统体验的关键因素。CPUDoc作为一款免费开源的CPU辅助工具,通过创…...
3步学会BilibiliDown:零基础掌握B站视频下载的终极指南
3步学会BilibiliDown:零基础掌握B站视频下载的终极指南 【免费下载链接】BilibiliDown (GUI-多平台支持) B站 哔哩哔哩 视频下载器。支持稍后再看、收藏夹、UP主视频批量下载|Bilibili Video Downloader 😳 项目地址: https://gitcode.com/gh_mirrors/…...
别再乱设target_frame了!深度解读ROS2 pointcloud_to_laserscan源码,搞懂tf转换与消息过滤器的正确用法
别再乱设target_frame了!深度解读ROS2 pointcloud_to_laserscan源码,搞懂tf转换与消息过滤器的正确用法 在机器人感知系统中,将三维点云数据转换为二维激光扫描数据是常见的降维处理手段。ROS2的pointcloud_to_laserscan功能包看似简单&…...
