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机试】全…...
变量 varablie 声明- Rust 变量 let mut 声明与 C/C++ 变量声明对比分析
一、变量声明设计:let 与 mut 的哲学解析 Rust 采用 let 声明变量并通过 mut 显式标记可变性,这种设计体现了语言的核心哲学。以下是深度解析: 1.1 设计理念剖析 安全优先原则:默认不可变强制开发者明确声明意图 let x 5; …...
Linux 文件类型,目录与路径,文件与目录管理
文件类型 后面的字符表示文件类型标志 普通文件:-(纯文本文件,二进制文件,数据格式文件) 如文本文件、图片、程序文件等。 目录文件:d(directory) 用来存放其他文件或子目录。 设备…...
【Web 进阶篇】优雅的接口设计:统一响应、全局异常处理与参数校验
系列回顾: 在上一篇中,我们成功地为应用集成了数据库,并使用 Spring Data JPA 实现了基本的 CRUD API。我们的应用现在能“记忆”数据了!但是,如果你仔细审视那些 API,会发现它们还很“粗糙”:有…...
【python异步多线程】异步多线程爬虫代码示例
claude生成的python多线程、异步代码示例,模拟20个网页的爬取,每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程:允许程序同时执行多个任务,提高IO密集型任务(如网络请求)的效率…...
Java毕业设计:WML信息查询与后端信息发布系统开发
JAVAWML信息查询与后端信息发布系统实现 一、系统概述 本系统基于Java和WML(无线标记语言)技术开发,实现了移动设备上的信息查询与后端信息发布功能。系统采用B/S架构,服务器端使用Java Servlet处理请求,数据库采用MySQL存储信息࿰…...
深入浅出Diffusion模型:从原理到实践的全方位教程
I. 引言:生成式AI的黎明 – Diffusion模型是什么? 近年来,生成式人工智能(Generative AI)领域取得了爆炸性的进展,模型能够根据简单的文本提示创作出逼真的图像、连贯的文本,乃至更多令人惊叹的…...
Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement
Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement 1. LAB环境2. L2公告策略2.1 部署Death Star2.2 访问服务2.3 部署L2公告策略2.4 服务宣告 3. 可视化 ARP 流量3.1 部署新服务3.2 准备可视化3.3 再次请求 4. 自动IPAM4.1 IPAM Pool4.2 …...
Python训练营-Day26-函数专题1:函数定义与参数
题目1:计算圆的面积 任务: 编写一个名为 calculate_circle_area 的函数,该函数接收圆的半径 radius 作为参数,并返回圆的面积。圆的面积 π * radius (可以使用 math.pi 作为 π 的值)要求:函数接收一个位置参数 radi…...
初探用uniapp写微信小程序遇到的问题及解决(vue3+ts)
零、关于开发思路 (一)拿到工作任务,先理清楚需求 1.逻辑部分 不放过原型里说的每一句话,有疑惑的部分该问产品/测试/之前的开发就问 2.页面部分(含国际化) 整体看过需要开发页面的原型后,分类一下哪些组件/样式可以复用,直接提取出来使用 (时间充分的前提下,不…...
mcts蒙特卡洛模拟树思想
您这个观察非常敏锐,而且在很大程度上是正确的!您已经洞察到了MCTS算法在不同阶段的两种不同行为模式。我们来把这个关系理得更清楚一些,您的理解其实离真相只有一步之遥。 您说的“select是在二次选择的时候起作用”,这个观察非…...
