leetcode148_排序链表的3种解法
- 1. 题目
- 2. 解答
- 2.1. 解法1
- 2.2. 解法2
- 2.3. 解法3
1. 题目
给你链表的头结点head
,请将其按升序排列并返回排序后的链表。
/*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode() : val(0), next(nullptr) {}* ListNode(int x) : val(x), next(nullptr) {}* ListNode(int x, ListNode *next) : val(x), next(next) {}* };*/
class Solution {
public:ListNode* sortList(ListNode* head) {}
};
样例:
输入:head = [4,2,1,3]
输出:[1,2,3,4]
输入:head = [-1,5,3,4,0]
输出:[-1,0,3,4,5]
输入:head = []
输出:[]
提示:
链表中节点的数目在范围 [0, 5 * 104] 内
-105 <= Node.val <= 105
进阶:
你可以在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序吗?
2. 解答
2.1. 解法1
把链表中每个节点的val
保存到一个额外的数组里,对数组进行排序,最后把数组里的值依次复制到链表的各个节点。
class Solution {
public:ListNode* sortList(ListNode* head) {vector<int> vi;ListNode *curNode = head;while (curNode) {vi.push_back(curNode->val);curNode = curNode->next;}sort(vi.begin(), vi.end());auto it = vi.begin();curNode = head;while (curNode) {curNode->val = *it;curNode = curNode->next;++it;}return head;}
};
时间复杂度:O(nlogn)
。性能瓶颈是sort
函数的时间复杂度。
空间复杂度:O(n)
,额外分配的数组跟链表是一个数量级的。
2.2. 解法2
自顶向下归并排序。
- 通过快慢指针把链表分割成两部分。
- 递归分别排序两个子链表。
- 使用归并排序的思路合并两个子链表。
class Solution {
public:ListNode* sortList(ListNode* head) {if (!head || !head->next) {return head;}ListNode *firstBegin = head;ListNode *firstEnd = head;ListNode *secondEnd = head->next;while (firstEnd && secondEnd) {if (secondEnd->next) {secondEnd = secondEnd->next->next;} else {break;}firstEnd = firstEnd->next;}ListNode *secondBegin = firstEnd->next;firstEnd->next = nullptr;ListNode *firstList = sortList(firstBegin);ListNode *secondList = sortList(secondBegin);return MergeList(firstList, secondList);}private:ListNode *MergeList(ListNode *firstList, ListNode *secondList){ListNode *fList = firstList;ListNode *sList = secondList;ListNode *list = new ListNode();ListNode *cur = list;while (fList && sList) {if (fList->val <= sList->val) {cur->next = fList;fList = fList->next;} else {cur->next = sList;sList = sList->next;}cur = cur->next;}cur->next = fList ? fList : sList;cur = list->next;delete list;return cur;}
};
时间复杂度:O(nlogn)
。
空间复杂度:O(logn)
,虽然是对链表做原地改动,但递归调用的栈空间达到了O(logn)
的空间复杂度。
虽然递归函数是自顶向下调用的,但它是自底向上返回的,因此需要栈空间保存一部分数据。
2.3. 解法3
自底向上归并排序。使用迭代代替递归,自底向上逐级排序。
- 将整个链表分成数个长度为
1
的子链表,每个子链表都是有序的。 - 将链表中相邻的每两个长度为
1
的子链表合并成一个长度为2
的有序的子链表。 - 将链表中相邻的每两个长度为
2
的子链表合并成一个长度为4
的有序的子链表。 - 以此类推,直到子链表的长度等于初始链表的长度。
class Solution {
public:ListNode* sortList(ListNode* head) {ListNode *curNode = head;int length = 0;while (curNode) {curNode = curNode->next;++length;}ListNode *mergedList = new ListNode(0, head);for (int subLength = 1; subLength <= length; subLength <<= 1) {curNode = mergedList->next;ListNode *mergedCur = mergedList;while (curNode) {ListNode *head1 = curNode;ListNode *tail1 = GetList(head1, subLength);if (!tail1) {// head1一定是有序的mergedCur->next = head1;break;}ListNode *head2 = tail1->next;tail1->next = nullptr;ListNode *tail2 = GetList(head2, subLength);if (tail2) {curNode = tail2->next;tail2->next = nullptr;}mergedCur->next = MergeList(head1, head2);while (mergedCur->next) {mergedCur = mergedCur->next;}if (!tail2) {break;}}}curNode = mergedList->next;delete mergedList;return curNode;}private:// 以head为第一个元素,获取长度为length的链表,返回最后一个元素的地址。// 如果返回nullptr说明获取的链表长度不足lengthListNode *GetList(ListNode *head, int length){ListNode *cur = head;for (int i = 1; i < length && cur; ++i) {cur = cur->next;}return cur;}ListNode *MergeList(ListNode *firstList, ListNode *secondList){ListNode *fList = firstList;ListNode *sList = secondList;ListNode *list = new ListNode();ListNode *cur = list;while (fList && sList) {if (fList->val <= sList->val) {cur->next = fList;fList = fList->next;} else {cur->next = sList;sList = sList->next;}cur = cur->next;}cur->next = fList ? fList : sList;cur = list->next;delete list;return cur;}
};
时间复杂度:O(nlogn)
,sortList
里for
循环的时间复杂度是O(logn)
,它内嵌的while
循环复杂度是O(n)
,相乘为O(nlogn)
。
空间复杂度:O(1)
。只额外分配了常数级的变量,无递归开销。
相关文章:

leetcode148_排序链表的3种解法
1. 题目2. 解答 2.1. 解法12.2. 解法22.3. 解法3 1. 题目 给你链表的头结点head,请将其按升序排列并返回排序后的链表。 /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode() : val(0), next(nullp…...

使用stm32实现电机的PID控制
使用stm32实现电机的PID控制 PID控制应该算是非常古老而且应用非常广泛的控制算法了,小到热水壶温度控制,大到控制无人机的飞行姿态和飞行速度等等。在电机控制中,PID算法用的尤为常见。 文章目录使用stm32实现电机的PID控制一、位置式PID1.计…...

数学原理—嵌入矩阵
目录 1.嵌入矩阵的基本作用 2.嵌入矩阵的数学解释 3.嵌入矩阵在联合分布适应中的数学推导主要包括以下几个步骤 4.在JDA中,怎么得到嵌入矩阵 5.联合分布自适应中如何得到嵌入矩阵 (另一种解释) 1.嵌入矩阵的基本作用 在机器学习中&a…...

English Learning - L2 语音作业打卡 辅音翘舌音 [ʃ] [ʒ] 空气摩擦音 [h] Day31 2023.3.23 周四
English Learning - L2 语音作业打卡 辅音翘舌音 [ʃ] [ʒ] 空气摩擦音 [h] Day31 2023.3.23 周四💌发音小贴士:💌当日目标音发音规则/技巧:翘舌音 [ʃ] [ʒ]空气摩擦音 [h]🍭 Part 1【热身练习】🍭 Part2【练习内容】…...

记录springboot+vue+fastdfs实现简易的文件(上传、下载、删除、预览)操作
前言说明:springboot vue FastDFS实现文件上传(支持预览)升级版 FASTDFS部分 FASTDFS安装过程:基于centos 7安装FastDFS文件服务器 SpringBoot部分 springboot源码实现 package com.core.doc.controller;import com.baomid…...

Java中循环使用Stream应用场景
在JAVA中,涉及到对数组、Collection等集合类中的元素进行操作的时候,通常会通过循环的方式进行逐个处理,或者使用Stream的方式进行处理。例如,现在有这么一个需求:从给定句子中返回单词长度大于5的单词列表,…...

中国蚁剑AntSword实战
中国蚁剑AntSword实战1.基本使用方法2.绕过安全狗连接3.请求包修改UA特征伪造RSA流量加密4.插件使用1.基本使用方法 打开蚂蚁宝剑,右键添加数据: 输入已经上传马的路径和连接密码: 测试连接,连接成功! GetShell了&…...

C++ 直接初始化和拷贝初始化
首先我们介绍直接初始化:编译器使用普通的函数匹配来选择与我们提供的参数最匹配的构造函数。文字描述可能会让你们云里雾里,那我们直接看代码: //先设计这样的一个类 class A{ public:A(){ cout << "A()" << endl; }A…...

数据迁移工具
1.Kettle Kettle是一款国外开源的ETL工具,纯Java编写,绿色无需安装,数据抽取高效稳定 (数据迁移工具)。 Kettle 中有两种脚本文件,transformation 和 job,transformation 完成针对数据的基础转换,job 则完成整个工作流的控制。 Kettle 中文名称叫水壶,该项目的主程序…...

【C/C++】程序的内存开辟
在C/C语言中,不同的类型开辟的空间区域都是不一样的. 这节我们就简单了解下开辟不同的类型内存所存放的区域在哪里. 文章目录栈区(stack)堆区(heap)数据段(静态区)常量存储区内存开辟布局图栈区…...

全网最完整,接口测试总结彻底打通接口自动化大门,看这篇就够了......
目录:导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜)前言 所谓接口࿰…...

28-flume和kafka为什么要结合使用
一:flume和kafka为什么要结合使用 首先:Flume 和 Kafka 都是用于处理大量数据的工具,但它们的设计目的不同。Flume 是一个可靠地收集、聚合和移动大量日志和事件数据的工具,而Kafka则是一个高吞吐量的分布式消息队列,…...

STM32外设-定时器详解
0. 概述 本文针对STM32F1系列,主要讲解了其中的8个定时器的原理和功能 1. 定时器分类 STM32F1 系列中,除了互联型的产品,共有 8 个定时器,分为基本定时器,通用定时器和高级定时器基本定时器 TIM6 和 TIM7 是一个 16 位…...

史上最详细的改良顺序表讲解,看完不会你打我
目录 0.什么是顺序表 1.顺序表里结构体的定义 2.顺序表的初始化 3.顺序表的输入 4.增加顺序表的长度 5.1顺序表的元素查找(按位查找) 5.2顺序表的元素查找(按值查找)在顺序表进行按值查找,大概只能通过遍历的方…...

【Unity入门】资源包导入和导出
【Unity入门】资源包导入和导出 大家好,我是Lampard~~ 欢迎来到Unity入门系列博客,所学知识来自B站阿发老师~感谢 (1)资源目录 Unity的资源(模型,场景,脚本)等都保存在Assert目录下&…...

python条件语句与循环语句
目录 一、条件语句 1.1if 二、循环语句 2.1while 2.2for循环 2.3break和continue 三、test和总结 一、条件语句 1.1if Python条件语句是通过一条或多条语句的执行结果(True或者False)来决定执行的代码块。 Python程序语言指定: 任…...

【leetcode】链表(2)
目录 1. 环形链表 解题思路 2. 环形链表 II 解题思路 3. 删除排序链表中的重复元素 解题思路 4. 删除排序链表中的重复元素 II 解题思路 5. 移除链表元素 解题思路 6. 链表的中间结点 解题思路 1. 环形链表 OJ:环形链表 给你一个链表的头节点 head &am…...

使用Vue+vue-router+路由守卫实现路由鉴权功能实战
目录 一、本节介绍和上节回顾 1. 上节介绍 2. Vue SpringBoot前后端分离项目实战的目录 3. 本小节介绍 二、Vue-router改造以及路由鉴权 1. 路由数据的拆分 2. 路由守卫 三、404错误页的实现 1. 创建全局css样式 2. 全局样式引入 3. 404页面的开发 4. el-button的…...

多线程(三):Thread 类的基本属性
上一个篇章浅浅了解了一下 线程的概念,进程与线程的区别,如何实现多线程编程。 而且上一章提到一个重要的面试点: start 方法和 run 方法的区别。 start 方法是从系统那里创建一个新的线程,这个线程会自动调用内部的run 方法&…...

蓝桥杯嵌入式第六课--串口收发
前言串口作为一个考试中考察频率较高的考点,其套路比较固定,因此值得我们仔细把握。本节课主要着眼于快速配置实现 串口收发与串口的中断。CubeMX配置选择串口2配置异步收发模式基本参数设置(波特率、校验位等等)开启串口收发中断…...

蓝桥杯冲刺 - Lastweek - 你离省一仅剩一步之遥!!!(掌握【DP】冲刺国赛)
文章目录💬前言🎯week3🌲day10-1背包完全背包多重背包多重背包 II分组背包🌲day2数字三角形 - 线性DP1015. 摘花生 - 数字三角形🌲day3最长上升子序列 - 线性DP1017. 怪盗基德的滑翔翼 - LIS1014.登山 - LIS最长公共子…...

C++ map与set的学习
1. 关联式容器在初阶阶段,我们已经接触过STL中的部分容器,比如:vector、list、deque、forward_list(C11)等,这些容器统称为序列式容器,因为其底层为线性序列的数据结构,里面存储的是元素本身。关联式容器也…...

【C语言初阶】函数
文章目录💐专栏导读💐文章导读🌷函数是什么?🌷函数的分类🌺库函数🌺自定义函数🌷函数的参数🌷函数的调用🌷函数的嵌套调用和链式访问🌺嵌套调用&a…...

CentOS 7安装redis6.2.6(包括服务开机自启和开放端口)
CentOS 7安装redis6.2.61. 官网下载redis文件2. 校验安装依赖2.1 安装系统默认版本gcc2.2 升级gcc版本3. 解压编译安装4. 修改配置redis.conf4.2 设置密码4.3 绑定ip(可选)5. 启动redis服务并测试5.2 测试安装是否成功5.3 redis开机自启配置6.开放防火墙…...

基于注解的自动装配~
Autowired:实现自动装配功能的注解 Autowired注解能够标识的位置: 标识在成员变量上,此时不需要设置成员变量的set方法标识在成员变量对应的set方法上标识在为当前成员变量赋值的有参构造上使用注解进行自动装配,只要在其成员变量…...

【深度学习】【分布式训练】Collective通信操作及Pytorch示例
相关博客 【深度学习】【分布式训练】Collective通信操作及Pytorch示例 【自然语言处理】【大模型】大语言模型BLOOM推理工具测试 【自然语言处理】【大模型】GLM-130B:一个开源双语预训练语言模型 【自然语言处理】【大模型】用于大型Transformer的8-bit矩阵乘法介…...

Spring常用注解说明
目录 1.常用注解 2.特别说明 3.xml及注解方式 1.常用注解 (1) SpringBootApplication (2) ControllerRestControllerRequestMappingRequestParamPathVariableGetMappingPostMappingPutMappingDeleteMappingResponseBodyRequestBodyCrossOrigin (3) ConfigurationBeanServ…...

13-C++面向对象(纯虚函数(抽象类)、多继承、多继承-虚函数、菱形继承、虚继承、静态成员)
虚析构函数 存在父类指针指向子类对象的情况,应该将析构函数声明为虚函数(虚析构函数) 纯虚函数 纯虚函数:没有函数体且初始化为0的虚函数,用来定义接口规范 抽象类: 含有纯虚函数的类,不可以实…...

Android DataBinding 自定义View实现数据双向绑定
看不懂的可以先看看单向数据绑定:Android DataBinding数据变化时自动更新界面_皮皮高的博客-CSDN博客 然后再确定已经启动了dataBinding的情况下,按下面的顺序来: 首先创建一个自定义View: import android.content.Context imp…...

网络安全中的渗透测试主要那几个方面
渗透测试中主要有软件测试和渗透测试。 1、测试对象不同 软件测试:主要测试的是程序、数据、文档。 渗透测试:对象主要为网络设备、主机操作系统、数据库系统和应用系统。 2、测试内容不同 软件测试:主要工作内容是验证和确认,发…...