Leetcode 148. 排序链表(二路归并)
题目:
给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。
解法一:
递归解法,自顶向下
链表版二路归并排序(升序,递归版),稳定排序
时间复杂度为 O(n*logn);空间复杂度:递归栈的深度 O(logn)
首先用快慢指针找到中心节点,以中心节点为左链表最后节点、递归左右俩链表,直到链表长度小于等于 1 回溯,
回溯时是将俩有序链表合并成一个有序链表,合并后将头尾嵌回原链表,然后继续回溯
注意:不能以 null 位结尾需要判断 tail 为结尾,最后要将排序好的链表头尾嵌回原链表,注意它是稳定排序处理
特别注意:由于回溯合并链表后,老的 head 位置改变,因此再次回溯决不能使用 head 判断,而是使用 virtual.next 才是正确头结点;当然也可以返回 newHead 节点
代码一:
/*** 链表版二路归并排序(升序,递归版),稳定排序* 时间复杂度为 O(n*logn);空间复杂度:递归栈的深度 O(logn)*/private ListNode solution(ListNode head) {// 判空if (head == null || head.getNext() == null) {return head;}// 添加虚拟节点指向头结点,方便处理ListNode virtual = new ListNode(0, head);// 递归版归并排序核心算法recursionMergeSort(virtual, head, null);return virtual.getNext();}/*** 首先用快慢指针找到中心节点,以中心节点为左链表最后节点、递归左右俩链表,直到链表长度小于等于 1 回溯,* 回溯时是将俩有序链表合并成一个有序链表,合并后将头尾嵌回原链表,然后继续回溯* 注意:不能以 null 位结尾需要判断 tail 为结尾,最后要将排序好的链表头尾嵌回原链表,注意它是稳定排序处理* 特别注意:由于回溯合并链表后,老的 head 位置改变,因此再次回溯决不能使用 head 判断,而是使用 virtual.next 才是正确头结点* @param virtual 当前链表头结点前一个节点* @param head 当前链表头结点* @param tail 当前链表尾结点后一个节点*/private void recursionMergeSort(ListNode virtual, ListNode head, ListNode tail) {// 小于等于 1 个节点回溯,注意结尾为 tailif (head == tail || head.getNext() == tail) {return;}// 快慢指针找到中心节点ListNode mid = getMidNode(virtual, tail);
// System.out.println("head:" + head);
// System.out.println("mid:" + mid);
// System.out.println("tail:" + tail + "\n");// 递归左右俩链表recursionMergeSort(virtual, head, mid.getNext());recursionMergeSort(mid, mid.getNext(), tail);// 左右俩有序链表合并成一个有序链表,返回新的头尾节点
// System.out.println("head:" + head);
// System.out.println("mid:" + mid);
// System.out.println("tail:" + tail + "\n");// 注意不能用使用回溯的 headListNode[] newNodes = mergeTwoSortedLinked(virtual.getNext(), mid.getNext(), tail);ListNode newHead = newNodes[0];ListNode newPreTail = newNodes[1];// 头尾节点嵌回原链表virtual.setNext(newHead);newPreTail.setNext(tail);
// System.out.println("virtual:" + virtual);
// System.out.println("newHead:" + newHead);
// System.out.println("newPreTail:" + newPreTail + "\n");}/*** 左右俩有序链表合并成一个有序链表,返回新的头尾节点* @param lHead 第一个链表头结点* @param rHead 第一个链表尾结点后一个节点,同时也是第二个链表的头结点* @param rTail 第二个链表尾结点后一个节点* @return 新的头尾节点:0-新头结点,1-新尾结点*/private ListNode[] mergeTwoSortedLinked(ListNode lHead, ListNode rHead, ListNode rTail) {ListNode newVirtual = new ListNode();ListNode newPreTail = newVirtual;ListNode l = lHead;ListNode r = rHead;while (l != rHead || r != rTail) {if (l == rHead) {newPreTail.setNext(r);newPreTail = r;r = r.getNext();} else if (r == rTail) {newPreTail.setNext(l);newPreTail = l;l = l.getNext();// 注意稳定排序规则} else if (l.getVal() <= r.getVal()) {newPreTail.setNext(l);newPreTail = l;l = l.getNext();} else {newPreTail.setNext(r);newPreTail = r;r = r.getNext();}}ListNode[] newNodes = new ListNode[2];newNodes[0] = newVirtual.getNext();newNodes[1] = newPreTail;return newNodes;}/*** 快慢指针找到中心节点* @param virtual 头结点前一个节点* @param tail 尾结点后一个节点* @return 中心节点*/private ListNode getMidNode(ListNode virtual, ListNode tail) {ListNode slow = virtual;ListNode fast = virtual;while (fast != tail && fast.getNext() != tail) {slow = slow.getNext();fast = fast.getNext().getNext();}return slow;}
解法二:
链表版二路归并排序(升序,迭代版自底向上),稳定排序
时间复杂度为 O(n*logn);空间复杂度:O(1)
先获取链表长度,然后分割成多组,每组连续俩节点排序,可看做两个有序链表(因为只有一个节点)合并成一个链表,注意最后一组可能不满,
接着连续四个节点排序,同样是两个有序链表合并成一个链表,然后连续八个、十六个…,直到排好序个数大于等于链表长度就完成
注意:链表每次排序后需要嵌回原链表
代码二:
/*** 链表版二路归并排序(升序,迭代版自底向上),稳定排序* 时间复杂度为 O(n*logn);空间复杂度:O(1)*/private ListNode solutionOptimization(ListNode head) {// 判空if (head == null || head.getNext() == null) {return head;}// 迭代版二路归并核心算法return iterationMergeSort(head);}/*** 先获取链表长度,然后分割成多组,每组连续俩节点排序,可看做两个有序链表(因为只有一个节点)合并成一个链表,注意最后一组可能不满,* 接着连续四个节点排序,同样是两个有序链表合并成一个链表,然后连续八个、十六个...,直到排好序个数大于等于链表长度就完成* 注意:链表每次排序后需要嵌回原链表* @return 返回排好序的新的头节点*/private ListNode iterationMergeSort(ListNode head) {// 获取链表长度int len = getLinkedLen(head);System.out.println("len:" + len + "\n");// 头结点前添加虚拟节点,方便操作ListNode virtual = new ListNode(0, head);// 循环操作连续两个节点、四个节点、八个节点...for (int group = 2; group / 2 < len; group *= 2) {// 第一个链表头结点前一个节点ListNode left = virtual;System.out.println("left:" + left);// 每 group 个元素一组,前 group/2 个与后 group/2 个有序链表合并成一个有序链表int halfGroup = group / 2;for (int now = 0; now < len; now += group) {System.out.println(String.format("now:%s group:%s left:%s", now, group, left));// 从第 now 位开始,最多到 len 长度,连续 group 个链表进行合并,然后 left 后移到下一组(每组头结点的前一个节点)left = mergeContinueLinked(group, halfGroup, now, len, left, left.getNext());}System.out.println();}return virtual.getNext();}/*** 获取链表长度*/private int getLinkedLen(ListNode head) {int len = 0;while (head != null) {len++;head = head.getNext();}return len;}/*** 从第 now 位开始,最多到 len 长度,连续 group 个链表进行合并,然后 left 后移* @param group 每组个数* @param halfGroup 半组长度,其为排好序的长度* @param now 当前下标(模拟数组)* @param len 链表总长度* @param preHead 当前链表起点位置的前一个节点* @param lHead 当前链表起点位置* @return lHead 接下来移到的位置的前一个节点*/private ListNode mergeContinueLinked(int group, int halfGroup, int now, int len, ListNode preHead, ListNode lHead) {// 后续不足 halfGroup 个元素,代表均已排if (len <= now + halfGroup) {return null;}// 第一个链表尾结点后一个节点,同时也是第二个链表头结点ListNode rHead = moveBack(lHead, halfGroup);// 第二个链表尾结点后一个节点ListNode rTail = moveBack(rHead, halfGroup);System.out.println(String.format("lHead:%s rHead:%s rTail:%s", lHead, rHead, rTail));// 合并俩有序链表ListNode[] newNodes = mergeTwoSortedLinked2(lHead, rHead, rTail);ListNode newHead = newNodes[0];ListNode newPreTail = newNodes[1];// 合并后的有序链表嵌回原链表preHead.setNext(newHead);newPreTail.setNext(rTail);System.out.println(String.format("preHead:%s newPreTail:%s", preHead, newPreTail) + "\n");return newPreTail;}/*** head 开始后移 halfGroup 个元素,其中移动到 null 则不用再移动了*/private ListNode moveBack(ListNode head, int halfGroup) {while (head != null && halfGroup > 0) {halfGroup--;head = head.getNext();}return head;}/*** 左右俩有序链表合并成一个有序链表,返回新的头尾节点* @param lHead 第一个链表头结点* @param rHead 第一个链表尾结点后一个节点,同时也是第二个链表的头结点* @param rTail 第二个链表尾结点后一个节点* @return 新的头尾节点:0-新头结点,1-新尾结点*/private ListNode[] mergeTwoSortedLinked2(ListNode lHead, ListNode rHead, ListNode rTail) {ListNode newVirtual = new ListNode();ListNode newPreTail = newVirtual;ListNode l = lHead;ListNode r = rHead;while (l != rHead || r != rTail) {if (l == rHead) {newPreTail.setNext(r);newPreTail = r;r = r.getNext();} else if (r == rTail) {newPreTail.setNext(l);newPreTail = l;l = l.getNext();// 保证排序稳定性} else if (l.getVal() <= r.getVal()) {newPreTail.setNext(l);newPreTail = l;l = l.getNext();} else {newPreTail.setNext(r);newPreTail = r;r = r.getNext();}}ListNode[] newNodes = new ListNode[2];newNodes[0] = newVirtual.getNext();newNodes[1] = newPreTail;return newNodes;}
相关文章:
Leetcode 148. 排序链表(二路归并)
题目: 给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。 解法一: 递归解法,自顶向下 链表版二路归并排序(升序,递归版),稳定排序 时间复杂度…...
记录Paint部分常用的方法
Paint部分常用的方法1、实例化之后Paint的基本配置2、shader 和 ShadowLayer3、pathEffect4、maskFilter5、colorFilter6、xfermode1、实例化之后Paint的基本配置 Paint.Align Align指定drawText如何将其文本相对于[x,y]坐标进行对齐。默认为LEFTPaint.Cap Cap指定了笔画线和路…...
ArrayList集合底层原理
ArrayList集合底层原理ArrayList集合底层原理1.介绍2.底层实现3.构造方法3.1集合的属性4.扩容机制5.其他方法6.总结ArrayList集合底层原理 1.介绍 ArrayList是List接口的可变数组的实现。实现了所有可选列表操作,并允许包括 null 在 内的所有元素。 每个 Array…...
内网部署swagger快解析映射方案发布让外网访问
计算机业内人士对于swagger并不陌生, 不少人选择用swagger做为API接口文档管理。Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。总体目标是使客户端和文件系统作为服务器以同样的速度来更新文件的方法&#x…...
全网最全整理,自动化测试10种场景处理(超详细)解决方案都在这......
目录:导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜)前言 自动化工作流程 自动…...
【c++】指针的学习
指针是C中非常重要的概念,理解指针的使用可以使程序更高效,并且可以处理更加复杂的数据结构。 指针是一个变量,它存储了另一个变量的地址。通过指针访问这个变量可以提高程序的效率,尤其是在处理大型数据结构时。 在C中࿰…...
华为OD机试题,用 Java 解【水仙花数】问题
华为Od必看系列 华为OD机试 全流程解析+经验分享,题型分享,防作弊指南)华为od机试,独家整理 已参加机试人员的实战技巧华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典使用说明 参加华为od机试,一定要注意不…...
【Linux】-- 基本指令
目录 用户管理 adduser passwd userdel pwd ls指令 -l -a -d -F -r -t -R -1 which alias ll ls -n cd cd - cd ~ touch -d stat mkdir -p rmdir rm -r -f man cp 编辑 -r -f mv cat -n tac more less -N head tail | 管道 dat…...
JavaScript 中的 String 类型 模板字面量定义字符串
ECMAScript 6新增了使用模板字面量定义字符串的能力。与使用单引号或双引号不同,模板字面量保留换行字符,可以跨行定义字符串: let str1 早起的年轻人\n喜欢经常跳步;let str2 早起的年轻人喜欢经常跳步;console.log(str1);// 早起的年轻人…...
我国防疫数据报告,2022年广东花费711亿,北京人均支出第一
哈喽大家好,2023年已经过去一段时间了,随着防疫策略的调整,小伙伴们是不是开始到处旅行购物了呢?当然了,对于自身的健康情况小伙伴们还是要多多关注,不要松懈。随着春节过后有序复工复产,各地纷…...
OpenCV-Python学习(22)—— OpenCV 视频读取与保存处理(cv.VideoCapture、cv.VideoWriter)
1. 学习目标 学习 OpenCV 的视频的编码格式 cv.VideoWriter_fourcc;学会使用 OpenCV 的视频读取函数 cv.VideoCapture;学会使用 OpenCV 的视频保存函数 cv.VideoWriter。 2. cv.VideoWriter_fourcc()常见的编码参数 2.1 参数说明 参数说明cv.VideoWr…...
2023-03-05力扣每日一题
链接: https://leetcode.cn/problems/triples-with-bitwise-and-equal-to-zero/ 题意: 模拟一个摩天轮,四个舱,每个舱最多四人,给一个数组,表示摩天轮每切换一次座舱会来多少人排队(人不会走…...
真正的IT技术男是什么样的?
我们经常会听到很多对IT男士的调侃称呼,“屌丝”、“宅男”,会逗的大家捧腹大笑。但是,大家要不要以为称呼IT男是“屌丝”、“宅男”,就当真以为他们是这样了。今天,青鸟学姐就带大家一起来了解一下,真正的…...
在函数中,用指针接收就可以改变相应的内容吗??
作者:小树苗渴望变成参天大树 作者宣言:认真写好每一篇博客 作者gitee:gitee 如 果 你 喜 欢 作 者 的 文 章 ,就 给 作 者 点 点 关 注 吧! 我们在不管指针那篇博客,还是在函数那篇博客中,我都给大家讲解过…...
Java+ElasticSearch+Pytorch实现以图搜图
以图搜图,涉及两大功能:1、提取图像特征向量。2、相似向量检索。第一个功能我通过编写pytorch模型并在java端借助djl调用实现,第二个功能通过elasticsearch7.6.2的dense_vector、cosineSimilarity实现。一、准备模型创建demo.py,输…...
【C语言学习笔记】:指针
指针的概念 指针是一个特殊的变量,它里面存储的数值被解释成为内存里的一个地址。要搞清一个指针需要搞清指针的四方面的内容:指针的类型,指针所指向的类型,指针的值或者叫指针所指向的内存区,还有指针本身所占据的内…...
微信小程序搭建流程
一、申请微信开发者账号虽然开发微信小程序可以使用工具提供的测试号,但是测试号提供的功能极为有限,而且使用测试号开发的微信小程序不能上架发布。因此说我们想要开发一个可以上架的微信小程序,首先必须要申请微信开发者账号。大家尽可放心…...
嵌入式 Linux进程间的通信--信号
目录 信号 信号的概述 信号类型 信号发送 1、kill 函数 2、raise函数 3、pause函数 信号处理 可以结合上一篇文章一起看: 嵌入式 Linux进程之间的通信_丘比特惩罚陆的博客-CSDN博客 信号 信号的概述 软中断信号(signal,又简称为…...
Vue3 核心模块源码解析(中)
【Vue3 核心模块源码解析(上)】讲到了 Vue2 与 Vue3的一些区别,Vue3 新特性的使用,以及略微带了一点源码。那么这篇文章就要从Vue3 模块源码解析 与 Vue3 执行逻辑解析这两个方面去给大家剖析 Vue3 的深层次,一起学习起来吧! 这里…...
华为OD机试题 - 剩余可用字符集(JavaScript)| 含思路
华为OD机试题 最近更新的博客使用说明本篇题解:剩余可用字符集题目输入输出示例一输入输出说明Code解题思路华为OD其它语言版本最近更新的博客 华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典【华为OD机试】全…...
css实现圆环展示百分比,根据值动态展示所占比例
代码如下 <view class""><view class"circle-chart"><view v-if"!!num" class"pie-item" :style"{background: conic-gradient(var(--one-color) 0%,#E9E6F1 ${num}%),}"></view><view v-else …...
Admin.Net中的消息通信SignalR解释
定义集线器接口 IOnlineUserHub public interface IOnlineUserHub {/// 在线用户列表Task OnlineUserList(OnlineUserList context);/// 强制下线Task ForceOffline(object context);/// 发布站内消息Task PublicNotice(SysNotice context);/// 接收消息Task ReceiveMessage(…...
ssc377d修改flash分区大小
1、flash的分区默认分配16M、 / # df -h Filesystem Size Used Available Use% Mounted on /dev/root 1.9M 1.9M 0 100% / /dev/mtdblock4 3.0M...
从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路
进入2025年以来,尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断,但全球市场热度依然高涨,入局者持续增加。 以国内市场为例,天眼查专业版数据显示,截至5月底,我国现存在业、存续状态的机器人相关企…...
连锁超市冷库节能解决方案:如何实现超市降本增效
在连锁超市冷库运营中,高能耗、设备损耗快、人工管理低效等问题长期困扰企业。御控冷库节能解决方案通过智能控制化霜、按需化霜、实时监控、故障诊断、自动预警、远程控制开关六大核心技术,实现年省电费15%-60%,且不改动原有装备、安装快捷、…...
【Go】3、Go语言进阶与依赖管理
前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课,做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程,它的核心机制是 Goroutine 协程、Channel 通道,并基于CSP(Communicating Sequential Processes࿰…...
工业自动化时代的精准装配革新:迁移科技3D视觉系统如何重塑机器人定位装配
AI3D视觉的工业赋能者 迁移科技成立于2017年,作为行业领先的3D工业相机及视觉系统供应商,累计完成数亿元融资。其核心技术覆盖硬件设计、算法优化及软件集成,通过稳定、易用、高回报的AI3D视觉系统,为汽车、新能源、金属制造等行…...
JVM 内存结构 详解
内存结构 运行时数据区: Java虚拟机在运行Java程序过程中管理的内存区域。 程序计数器: 线程私有,程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都依赖这个计数器完成。 每个线程都有一个程序计数…...
20个超级好用的 CSS 动画库
分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码,而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库,可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画,可以包含在你的网页或应用项目中。 3.An…...
多模态图像修复系统:基于深度学习的图片修复实现
多模态图像修复系统:基于深度学习的图片修复实现 1. 系统概述 本系统使用多模态大模型(Stable Diffusion Inpainting)实现图像修复功能,结合文本描述和图片输入,对指定区域进行内容修复。系统包含完整的数据处理、模型训练、推理部署流程。 import torch import numpy …...
