坚持刷题|合并有序链表

文章目录

  • 题目
  • 思考
  • 代码实现
    • 迭代
    • 递归
  • 扩展
    • 实现k个有序链表合并
      • 方法一
      • 方法二
    • PriorityQueue
      • 基本操作
      • Java示例
      • 注意事项

Hello,大家好,我是阿月。坚持刷题,老年痴呆追不上我,消失了一段时间,我又回来刷题啦,今天先刷个简单的:合并有序链表

题目

21. 合并两个有序链表
在这里插入图片描述

思考

合并有序链表这一算法问题主要考察以下几个关键点

  1. 链表操作:理解链表数据结构以及如何遍历、比较、连接链表节点。
  2. 边界条件处理:包括处理一个或两个链表为空的情况,以及其中一个链表先到达末尾的场景。
  3. 递归与迭代:可以选择递归或迭代的方法来解决问题,每种方法都有其优缺点,需要理解何时使用哪一种。
  4. 效率考量:优化算法的时间复杂度和空间复杂度,例如使用优先队列可以达到较好的时间性能。
  5. 代码整洁性:写出清晰、可读性强的代码,避免不必要的复杂性和冗余。
  6. 错误处理:在实际编码中,考虑到可能出现的异常情况,比如处理null指针异常。
  7. 算法设计:理解不同算法策略,如两两合并法、使用辅助数据结构等。
  8. 优化技巧:了解如何在不增加额外空间复杂度的情况下解决问题,例如原地修改链表而非创建新的链表。
  9. 测试用例:编写全面的测试用例验证代码的正确性,包括极端情况和常见情况。
  10. 性能分析:能够分析和解释算法的时间和空间复杂度,理解为什么某种方法优于其他方法。

代码实现

合并两个有序链表是一个常见的链表操作,可以通过迭代或递归的方式实现。

迭代

public class ListNode {int val;ListNode next;ListNode(int x) { val = x; }
}public class Solution {public ListNode mergeTwoLists(ListNode l1, ListNode l2) {ListNode dummy = new ListNode(0);ListNode current = dummy;while (l1 != null && l2 != null) {if (l1.val < l2.val) {current.next = l1;l1 = l1.next;} else {current.next = l2;l2 = l2.next;}current = current.next;}current.next = l1 == null ? l2 : l1;return dummy.next;}
}

在这段代码中,我们首先创建一个虚拟头节点dummy和一个指针cur指向dummy。然后我们遍历两个链表,每次都把较小的节点接到cur的后面,并移动cur和较小节点所在链表的头指针。当一个链表遍历完后,我们就直接把另一个链表接到cur的后面。最后返回dummy.next就是合并后的链表。

递归

使用递归的方式来合并两个有序链表是一种直观且简洁的方法。以下是使用Java实现的示例代码:

public class ListNode {int val;ListNode next;ListNode(int x) { val = x; }
}public class Solution {public ListNode mergeTwoLists(ListNode l1, ListNode l2) {if (l1 == null) {return l2;}if (l2 == null) {return l1;}if (l1.val < l2.val) {l1.next = mergeTwoLists(l1.next, l2);return l1;} else {l2.next = mergeTwoLists(l1, l2.next);return l2;}}
}

在上述代码中,mergeTwoLists方法接受两个链表的头节点l1l2作为参数。该方法首先检查两个链表是否为空。如果其中一个链表为空,那么就直接返回另一个链表,因为这意味着另一个链表已经是合并后的结果。

如果两个链表都不为空,那么就比较两个链表头节点的值。如果l1的值小于l2的值,那么就将l1next字段设置为l1.nextl2合并的结果,然后返回l1。反之亦然,如果l2的值小于等于l1的值,那么就将l2next字段设置为l1l2.next合并的结果,然后返回l2

这种方法利用了递归的特性,逐步将大问题分解成小问题,直到问题简单到可以直接解决(即一个链表为空)。由于递归的深度最多为链表的长度,因此这种方法的时间复杂度为O(n),其中n为两个链表中节点的总数。

扩展

实现k个有序链表合并

方法一

我们可以使用分治的策略,将K个链表两两合并,直到剩下最后一个链表。这就是所谓的"两两合并"策略。以下是使用Java实现的代码:

public class ListNode {int val;ListNode next;ListNode(int x) { val = x; }
}public class Solution {public ListNode mergeKLists(ListNode[] lists) {int amount = lists.length;if(amount==0){return null;}int interval = 1;while (interval < amount) {for (int i = 0; i < amount - interval; i += interval * 2) {lists[i] = mergeTwoLists(lists[i], lists[i + interval]);}interval *= 2;}return lists[0];}private ListNode mergeTwoLists(ListNode l1, ListNode l2) {ListNode dummy = new ListNode(0);ListNode current = dummy;while (l1 != null && l2 != null) {if (l1.val < l2.val) {current.next = l1;l1 = l1.next;} else {current.next = l2;l2 = l2.next;}current = current.next;}current.next = l1 == null ? l2 : l1;return dummy.next;}
}

在这个代码中,我们首先定义了一个mergeTwoLists方法用于合并两个有序链表。然后在mergeKLists方法中,我们使用了"两两合并"的策略来合并K个有序链表。
这种方法的时间复杂度为O(N log K),其中N是所有链表中节点的总数,K是链表的数量。空间复杂度为O(1)。

方法二

在Java中,我们还可以使用 PriorityQueue 来实现。

import java.util.PriorityQueue;public class ListNode {int val;ListNode next;ListNode(int x) { val = x; }
}public class Solution {public ListNode mergeKLists(ListNode[] lists) {PriorityQueue<ListNode> queue = new PriorityQueue<>((a, b) -> a.val - b.val);ListNode dummy = new ListNode(0);ListNode tail = dummy;for (ListNode node : lists) {if (node != null) {queue.add(node);}}while (!queue.isEmpty()) {ListNode node = queue.poll();tail.next = node;tail = tail.next;if (node.next != null) {queue.add(node.next);}}return dummy.next;}
}

在这个解决方案中,我们首先创建一个虚拟头节点和一个尾节点,然后遍历所有的链表,将每个链表的头节点放入优先队列中。然后我们开始循环处理优先队列,每次从队列中取出最小的元素,将其添加到结果链表中,然后将其下一个节点(如果存在)放入队列中。最后返回虚拟头节点的下一个节点,即为合并后的链表的头节点。

  • 注意,Java中的 PriorityQueue 默认是按照自然顺序排序的,所以我们需要提供一个比较器来确保按照节点的值进行排序。PriorityQueue是Java集合框架的一部分,它是一个基于优先堆的无界优先队列。此队列按照元素的自然排序进行排序,或者根据构造队列时提供的Comparator进行排序,具体取决于使用的构造方法。

PriorityQueue

最小堆是一种特殊的完全二叉树结构,其中每个父节点的值都小于或等于其子节点的值。这种结构保证了堆的根节点始终是最小的元素,从而使得查找最小元素的操作非常快速。最小堆在Java中可以通过java.util.PriorityQueue类来实现,因为这个类内部就是使用数组实现的最小堆。

基本操作

  1. 插入元素 (offeradd):将一个元素添加到堆中,同时保持堆的性质不变,但是当队列已满时,add会抛出异常,而offer则返回false。
  2. 删除最小元素 (poll):从堆中移除并返回最小的元素。
  3. 获取最小元素 (peek):返回但不移除最小的元素。
  4. 移除并返回队列头部的元素(remove):如果队列为空,则抛出NoSuchElementException异常。

Java示例

首先,我们创建一个PriorityQueue实例,这将作为我们的最小堆:

import java.util.PriorityQueue;public class MinHeapExample {public static void main(String[] args) {// 创建一个最小堆PriorityQueue<Integer> minHeap = new PriorityQueue<>();// 插入元素minHeap.offer(5);minHeap.offer(1);minHeap.offer(3);minHeap.offer(4);System.out.println("堆中的元素:" + minHeap);// 获取最小元素Integer smallest = minHeap.peek();System.out.println("最小元素是:" + smallest);// 删除最小元素Integer removed = minHeap.poll();System.out.println("删除的最小元素:" + removed);System.out.println("删除后堆中的元素:" + minHeap);}
}

运行上述代码,你将看到如下输出:

堆中的元素:[1, 4, 3, 5]
最小元素是:1
删除的最小元素:1
删除后堆中的元素:[3, 4, 5]

注意事项

  • PriorityQueue默认情况下实现的是最小堆,如果要实现最大堆,可以自定义比较器Comparator
  • PriorityQueue不允许插入null元素,否则会抛出NullPointerException
  • PriorityQueue没有固定大小,它可以动态调整大小。
  • 通过上面的示例,可以看到PriorityQueue如何在内部自动维护堆的性质,使得最小元素始终位于堆的顶部,方便我们进行高效的查找和删除操作。
  • PriorityQueue的一个重要特性是它的元素必须是可比较的,也就是说,它们必须实现了Comparable接口,或者在创建PriorityQueue时提供了一个Comparator。

例如,下面的代码创建了一个按照字符串长度排序的PriorityQueue

PriorityQueue<String> pq = new PriorityQueue<>(new Comparator<String>() {public int compare(String s1, String s2) {return s1.length() - s2.length();}
});

在这个例子中,我们提供了一个Comparator,它将字符串按照长度进行排序。

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

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

相关文章

【调试笔记-20240612-Linux-在 QEMU 中配置 OpenWrt-23.05 支持访问 Windows 宿主机的共享目录】

调试笔记-系列文章目录 调试笔记-20240612-Linux-在 QEMU 中配置 OpenWrt-23.05 支持访问 Windows 宿主机的共享目录 文章目录 调试笔记-系列文章目录调试笔记-20240612-Linux-在 QEMU 中配置 OpenWrt-23.05 支持访问 Windows 宿主机的共享目录 前言一、调试环境操作系统&…

大模型泡沫退去,谁能活到下半场?

前言 从今年3月开始&#xff0c;国内企业纷纷下场大模型&#xff0c;铆足劲秀肌肉&#xff0c;如今转向垂直行业淘金&#xff0c;试图争霸行业大模型。我们的心态也逐渐从看乐子&#xff0c;到严肃讨论。 在人工智能的世界&#xff0c;我们经历了众多的概念游戏&#xff0c;在…

2021 hnust 湖科大 数据结构课堂实验代码及报告

2021 hnust 湖科大 数据结构课堂实验代码及报告 目录 实验1 线性表 1 实验2 栈的应用 4 实验3 队列的应用 9 实验4 串的应用 16 实验5 二叉树遍历 19 实验6 哈夫曼编码 23 实验7 图的遍历 27 实验8 最小生成树 32 实验9 最短路径 35 实验10 折半查找 37 实验11 插入排序与交换…

python实践笔记(三): 异常处理和文件操作

1. 写在前面 最近在重构之前的后端代码&#xff0c;借着这个机会又重新补充了关于python的一些知识&#xff0c; 学习到了一些高效编写代码的方法和心得&#xff0c;比如构建大项目来讲&#xff0c;要明确捕捉异常机制的重要性&#xff0c; 学会使用try...except..finally&…

《Java2实用教程》 期末考试整理

作用域 当前类 当前包 子类 其他包 public √ √ √ √ protected √ √ √ default √ √ private √ 三、问答题&#xff08;每小题4分&#xff0c;共8分&#xff09; 1.类与对象的关系 对象&#xff1a;对象是类的一个实例&#xff0c;有状…

【实用工具】Skyworking

文章目录 一、Skywalking概述1.1 核心功能1.2 指标概述1.2.1 一些常用指标1.2.2 指标的使用方式 二、仪表盘2.1 APM2.1.1 全局维度的指标2.1.2 服务维度的指标2.1.3 实例维度的指标2.1.4 端点维度的指标 2.2 数据库2.3 其他 三、追踪3.1 界面划分3.2 请求链路界面分析3.3 一般流…

Diffusion 扩散模型(DDPM)

Diffusion 扩散模型&#xff08;DDPM&#xff09; 一、什么是扩散模型&#xff1f; 随着Stable Diffusion 3的问世&#xff0c;AI绘画再次成为最为火热的AI方向之一&#xff0c;那么不可避免地再次会问到Stable Diffusion里的这个”Diffusion”到底是什么&#xff1f;其实扩散…

typora下载安装、激活教程

目录 介绍 基本功能 使用教程 高级功能 下载安装 激活 关闭软件每次启动时的已激活弹窗 去除软件左下角“未激活”提示 介绍 Typora 是一款功能强大的 Markdown 编辑器&#xff0c;它以其简洁的界面设计和高效的文本编辑能力受到许多用户的青睐。Typora 的主要特点在于…

Socket编程模型

一、整体过程图解 二、Socket编程模型细节 客户端和服务器能在网络中通信&#xff0c;那必须得使用Socket编程&#xff0c;它是进程间通信里比较特别的方式,特别之处在于它是可以跨主机间通信。 创建Socket 的时候&#xff0c;可以指定网络层使用的是IPv4还是IPv6&#xff0c;传…

jvm必知必会-类的生命周期图文详解

类的生命周期描述了一个从加载、使用到卸载的过程; 而其中的 连接 部分又分为一下三个阶段: 验证准备解析6.1 加载阶段 Loading阶段第一步是 类加载器 会根据类全限定名通过不同的渠道以二进制流的方式获取字节码信息,程序员可以使用Java代码扩展不同的渠道。 比如通过 …

LeetCode 算法:螺旋矩阵c++

原题链接&#x1f517;&#xff1a;螺旋矩阵 难度&#xff1a;中等⭐️⭐️ 题目 给你一个 m 行 n 列的矩阵 matrix &#xff0c;请按照 顺时针螺旋顺序 &#xff0c;返回矩阵中的所有元素。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,2,3],[4,5,6],[7,8,9]] 输出&…

实用技巧:跳过TCODE权限检查ALINK_CALL_TRANSACTION

RFC&#xff1a;ALINK_CALL_TRANSACTION 遇到tcode 提示没有权限打开&#xff0c;可以通过这个RFC,debug 修改检查值&#xff0c;打开TCODE。 适用于紧急情况 断点打在20行&#xff0c;SY-SUBRC 的值改成 1

【ARMv8/ARMv9 硬件加速系列 3.4 -- SVE 复制指令CPY 使用介绍】

文章目录 SVE 复制指令CPYSVE 指令格式SVE 使用语法SVE CPY 使用示例SVE CPY 小结SVE 复制指令CPY CPY <Zd>.<T>, <Pg>/M, #<imm>{, <shift>}cpy 指令在 ARMv9 的

MySQL的卸载

先停止对应MySQL服务&#xff0c;然后再进行删除&#xff08;进入MySQL安装目录bin目录内以管理员身份打开命令行&#xff09; 停止服务 net stop Mysql&#xff08;服务名&#xff09; ——MySQL&#xff08;服务名&#xff09;安装时是什么名字就写什么名字 卸载 mysqld …

在矩池云快速使用ChatTTS,文本转语音太酷了

ChatTTS 最新的一款文本转语音模型&#xff0c;目前支持英文和中文两种语言&#xff0c;面向对话场景的转化则更为精准&#xff0c;在汉语的语音韵律方面超越了以往很多同类模型&#xff0c;此外它还支持细粒度控制&#xff0c;允许在文字中加入笑声、停顿、语气词等&#xff0…

不会策划营销活动?教你一步步成为策划高手

要想让活动大获成功&#xff0c;不仅需要创意十足&#xff0c;更要有严谨的策划和执行&#xff0c;确实新人会有点感觉不知所措。 但其实也不用怕&#xff0c;只要按照以下五个关键步骤&#xff0c;一步步来&#xff0c;也可以轻松策划及格的好活动。 步骤一&#xff1a;锁定目…

建筑垃圾/城市固废倾倒转移乱象:EasyCVR+AI智能视频监控方案助力城市环保监管

近日有新闻记者报道&#xff0c;中央生态环境保护督察组在上海、浙江、江西、湖北、湖南、重庆、云南7省市督察发现&#xff0c;一些地方建筑垃圾处置工作存在明显短板&#xff0c;乱堆乱倒问题时有发生&#xff0c;比如&#xff0c;江西湘东区在杨家田地块违规设置弃土场&…

Confluence安装

Confluence安装 1.安装 #下载confluence版本&#xff08;8.5.11&#xff09; https://www.atlassian.com/software/confluence/download-archives #修改权限 chmod x atlassian-confluence-8.5.11-x64.bin #执行安装 ./atlassian-confluence-8.5.11-x64.bin按照以下提示输入&…

WebGIS如何加载微件

本篇文章以加载切换底图微件做示范 首先&#xff0c;添加require "esri/widgets/ScaleBar",//比例尺"esri/widgets/Legend",//图例"esri/widgets/basemapGallery" 然后添加加载切换底图的组件代码 const basemapGallery new BasemapGallery(…

【乳业巨擘·数字革命先锋】光明乳业:上市公司科技蜕变,搭贝低代码引领未来新纪元

在这个由科技编织的未来世界里&#xff0c;光明乳业股份有限公司以巨人之姿&#xff0c;傲立于乳业之巅&#xff0c;以其无与伦比的胆识与魄力&#xff0c;引领了一场震撼业界的数字化革命。与低代码领域的创新领袖——搭贝的强强联合&#xff0c;不仅标志着光明乳业在数字化转…