【LeetCode】148. 排序链表

news2025/1/24 8:37:12

148. 排序链表(中等)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

方法一:归并排序(递归法)

思路

  • 题目要求时间空间复杂度分别为 O(nlogn) 和 O(1) ,根据时间复杂度我们自然想到二分法,从而联想到归并排序

  • 对数组做归并排序的空间复杂度为 O(n) ,分别由新开辟数组 O(n) 和递归函数调用 O(logn) 组成,而根据链表特性:

    • 数组额外空间:链表可以通过修改引用来更改节点顺序,无需像数组一样开辟额外空间;
    • 递归额外空间:递归调用函数将带来 O(logn) 的空间复杂度,因此若希望达到 O(1) 空间复杂度,则不能使用递归。
  • 通过递归实现链表归并排序,有以下两个环节:

    • 分割 cut 环节: 找到当前链表 中点,并从中点将链表断开(以便在下次递归 cut 时,链表片段拥有正确边界);
      • 我们使用 fast,slow 快慢双指针法,「奇数个节点找到中点,偶数个节点找到中心左边的节点」。
      • 找到中点 slow 后,执行 slow->next = nullptr; 将链表切断。
      • 递归分割时,输入当前链表左端点 head 和中心节点 slow 的下一个节点 tmp(因为链表是从 slow 切断的)。
      • cut 递归终止条件: 当 head->next == nullptr 时,说明只有一个节点了,直接返回此节点。
    • 合并 merge 环节: 将两个排序链表合并,转化为一个排序链表。
      • 双指针法合并,建立辅助 ListNode* h 作为头部。
      • 设置两指针 left, right 分别指向两链表头部,比较两指针处节点值大小,由小到大加入合并链表头部,指针交替前进,直至添加完两个链表。
        返回辅助ListNode* h 作为头部的下个节点 h->next
      • 时间复杂度 O(l + r),l, r 分别代表两个链表长度。
    • 当题目输入的 head == nullptr 时,直接返回 None。

在这里插入图片描述

代码

/**
 * 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) {
        if (head == nullptr || head->next == nullptr) {
            return head;
        }
        // 定义快慢指针找到分割中点 slow
        ListNode* fast = head->next;
        ListNode* slow = head;
        while(fast && fast->next) {
            slow = slow->next;
            fast = fast->next->next;
        }
        // 右半边的头节点 tmp
        ListNode* tmp = slow->next;
        // 左右分割
        slow->next = nullptr;
        // 递归分割
        ListNode* left = sortList(head);
        ListNode* right = sortList(tmp);

        // 定义辅助头节点 h
        ListNode* h = new ListNode(0);
        ListNode* res = h; // res保存h的头节点

        // 合并
        while (left && right) {
            if (left->val < right->val) {
                h->next = left;
                left = left->next;
            }
            else {
                h->next = right;
                right = right->next;
            }
            h = h->next;
        }
        // 检查是left为空还是right为空
        h->next = left != nullptr ? left : right;
        return res->next;
    }
};

方法二:归并排序(从底至顶直接合并)

思路

  • 对于非递归的归并排序,需要使用迭代的方式替换 cut 环节:

    • cut 环节本质上是通过二分法得到链表最小节点单元,再通过多轮合并得到排序结果。
    • 每一轮合并 merge 操作针对的单元都有固定长度 intv,例如:
      • 第一轮合并时 intv = 1,即将整个链表切分为多个长度为 1 的单元,并按顺序两两排序合并,合并完成的已排序单元长度为 2。
      • 第二轮合并时 intv = 2,即将整个链表切分为多个长度为 2 的单元,并按顺序两两排序合并,合并完成已排序单元长度为 4。
      • 以此类推,直到单元长度 intv >= 链表长度,代表已经排序完成。
    • 根据以上推论,我们可以仅根据 intv 计算每个单元边界,并完成链表的每轮排序合并,例如:
      • 当 intv = 1 时,将链表第 1 和第 2 节点排序合并,第 3 和第 4 节点排序合并,……。
      • 当 intv = 2 时,将链表第 1-2 和第 3-4 节点排序合并,第 5-6 和第 7-8 节点排序合并,……。
      • 当 intv = 4 时,将链表第 1-4 和第 5-8 节点排序合并,第 9-12 和第 13-16 节点排序合并,……。
  • 此方法时间复杂度 O(nlogn) ,空间复杂度 O(1) 。

在这里插入图片描述

  • 模拟上述的多轮排序合并:

    • 统计链表长度 length,用于通过判断 intv < length 判定是否完成排序;
    • 额外声明一个节点 res,作为头部后面接整个链表,用于:
      • intv *= 2 即切换到下一轮合并时,可通过 res->next 找到链表头部 h;
      • 执行排序合并时,需要一个辅助节点作为头部,而 res 则作为链表头部排序合并时的辅助头部 pre;后面的合并排序可以将上次合并排序的尾部 tail 用做辅助节点。
    • 在每轮 intv 下的合并流程:
      • 根据 intv 找到合并单元 1 和单元 2 的头部 h1, h2。由于链表长度可能不是 2^n,需要考虑边界条件
        • 在找 h2 过程中,如果链表剩余元素个数少于 intv,则无需合并环节,直接 break,执行下一轮合并;
        • 若 h2 存在,但以 h2 为头部的剩余元素个数少于 intv,也执行合并环节,h2 单元的长度为 c2 = intv - i。
      • 合并长度为 c1, c2 的 h1, h2 链表,其中:
        • 合并完后,需要修改新的合并单元的尾部 pre 指针指向下一个合并单元头部 h。(在寻找 h1, h2 环节中,h指针已经被移动到下一个单元头部
        • 合并单元尾部同时也作为下次合并的辅助头部 pre。
      • 当 h == None,代表此轮 intv 合并完成,跳出。
    • 每轮合并完成后将单元长度 ×2,切换到下轮合并:intv *= 2。

代码

/**
 * 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) {
        if (head == nullptr || head->next == nullptr) return head;
        int intv = 1, length = 0;
        // res保存已排序的结果链表头节点
        ListNode* res = new ListNode(0);
        res->next = head;
        // pre指向已排序的结果链表的末尾
        // h指向待排序链表的头节点
        ListNode* pre;
        ListNode* h;
        // 遍历链表,得到长度
        while (head) {
            length ++;
            head = head->next;
        }
        while (intv < length) {
            pre = res;
            h = res->next;
            // 当待排序链表头节点不为空,说明还需要归并
            while(h) {
                // tmp1保存合并单元1的头节点
                ListNode* tmp1 = h;
                int len1 = intv;
                while (len1 > 0 && h) {
                    len1 --;
                    h = h->next;
                } 
                if(len1 > 0) {
                    break;
                }
                // tmp2保存合并单元2的头节点
                ListNode* tmp2 = h;
                int len2 = intv; 
                while (len2 > 0 && h) {
                    len2 --;
                    h = h->next;
                }
                // c1 c2分别是合并单元剩余的节点(即合并单元各自的节点数)
                int c1 = intv, c2 = intv - len2;
                while (c1 > 0 && c2 > 0) {
                    if (tmp1->val > tmp2->val) {
                        pre->next = tmp2;
                        tmp2 = tmp2->next;
                        c2 --; // 减少一个节点
                    }
                    else {
                        pre->next = tmp1;
                        tmp1 = tmp1->next;
                        c1--;
                    }
                    pre = pre->next;
                }
                // 将c1剩余节点连接到已排序节点的末尾
                if(c1 > 0) {
                    pre->next = tmp1;
                }
                else {
                    pre->next = tmp2;
                }
                // 更新pre 使其指向已排序链表的末尾
                while (c1-- > 0 || c2-- > 0) {
                    pre = pre->next;
                }
                // 将已排序链表和待排序链表连接
                pre->next = h;
            }
            intv *= 2;
        }
        return res->next;
    }
};

参考资料

  1. Sort List (归并排序链表)

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

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

相关文章

毫米波雷达在环境监测中的关键作用

随着环境问题的日益凸显&#xff0c;精确、实时的环境监测成为了保护地球的关键一环。在这个背景下&#xff0c;毫米波雷达技术逐渐崭露头角&#xff0c;以其在环境监测中的独特优势成为不可或缺的工具。本文将探讨毫米波雷达在环境监测中的关键作用&#xff0c;以及它是如何应…

Unity3D 基础——使用 Vector3.Lerp 实现缓动效果

让一个物体从当前位置移动到另一个位置 Vector3-Lerp - Unity 脚本 APIhttps://docs.unity.cn/cn/current/ScriptReference/Vector3.Lerp.html 1.在场景中新建两个 Cube 立方体&#xff0c;在 Scene 视图中将两个 Cude的位置错开。 2.新建 C# 脚本 MoveToTarget.cs&#xff08…

MS5542数模转换器可pin对pin兼容DAC8831

MS5541/MS5542 是一款单通道、16 位、串行输入、电压输出的数模转换器&#xff0c;采用 2.7V 至 5.5V 单电源供电&#xff0c;输出范围为 0V 至 VREF。在输出范围内保证单调性&#xff0c;在温度范围为-40C至85C 能够提供 1LSB INL 的 14 位精度。MS5541/MS5542 提供无缓冲输出…

如何绕过api的防重放做安全测试

一、问题引入&#xff1a;api接口测试&#xff0c;会检测请求头中的nonce参数的值&#xff0c;每次请求的值必须不同&#xff0c;否则发包失败 笔者在进行api接口的测试时&#xff08;因为菜没有工具&#xff0c;只能另辟蹊跷&#xff09;&#xff0c;使用postmanxray进行安全测…

【LeetCode】《LeetCode 101》第十三章:链表

文章目录 13.1 数据结构介绍13.2 链表的基本操作206. 反转链表&#xff08;简单&#xff09;21. 合并两个有序链表&#xff08;简单&#xff09;24.两两交换链表中的节点&#xff08;中等&#xff09; 13.3 其它链表技巧160. 相交链表&#xff08;简单&#xff09;234. 回文链表…

Typora +Picgo 搭建个人笔记

文章目录 Typora Picgo 搭建个人笔记一、Picgo Github 搭建图床1.基础设置2. 将配置导出&#xff0c;方便下次使用 二、Typora&#xff1a;设置 &#xff1a;1. 基本设置2. 导出自动提交3. 备份图片 Typora Picgo 搭建个人笔记 typora 下载地址&#xff1a; https://zahui.fan…

使用UniApp实现视频数组自动下载与播放功能:一步步指导

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

阶段性总结

uart协议&#xff1a; 通用异步收发器 UART&#xff08;Universal Asynchronous Receiver/Transmitter)&#xff0c;是一种串行、异步、全双工的通信协议&#xff0c;将所需传输的数据一位接一位地传输&#xff0c;在UART通讯协议中信号线上的状态位高电平代表’1’&#xff0…

Java应用性能问题诊断技巧

作者&#xff1a;张彦东 参考&#xff1a;https://developer.aliyun.com/ebook/450?spma2c6h.20345107.ebook-index.28.6eb21f54J7SUYc 文章目录 &#xff08;一&#xff09;内存1.内存2.内存-JMX3.内存-Jmap4.内存-结合代码确认问题 &#xff08;二&#xff09;CPU1.CPU-JMX或…

短视频矩阵系统源码开发

短视频剪辑矩阵系统开发源码----源头搭建 一、源码技术构建源码部署搭建交付之---- 1.需要协助系统完成部署、接口全部正常接入、系统正常运行多久&#xff1f;7个工作日 2.需要准备好服务器以及备案域名 3.短视频SEO模块一年项目带宽&#xff0c;带宽最低要求10M&#xff0c;…

如何将电脑上的“小电影”隐藏为一张图片?这波操作绝了!!

大家好&#xff0c;我是冰河~~ 最近&#xff0c;有很多小伙伴想跟我学渗透。平时时间确实太忙了&#xff0c;除了要研发公司项目外&#xff0c;写公号&#xff0c;写博客&#xff0c;录视频&#xff0c;写书稿&#xff0c;维护开源项目&#xff0c;几乎占据了我全部的业余时间…

【性能测试】使用JMeter对code论坛进行压力测试

1.项目介绍 项目简介 code 论坛是一个技术交流社区。主要功能有发布帖子&#xff0c;查看帖子&#xff0c;评价帖子&#xff0c;删除帖子&#xff0c;点赞帖子&#xff0c;站内信&#xff0c;个人中心&#xff0c;修改个人信息等。是一个基于 Spring 的前后端分离项目。 项目链…

vue3自定义指令批量注册

第一步、在src目录下新建directives文件文件夹 用来存放不同的指令&#xff0c;以dbounce指令为例&#xff1a; 第二步、在directives目录下创建debounce.js文件&#xff0c;文件内容如下&#xff1a; // 防抖 const debounceClick {mounted(el, binding) {let timerel.addE…

点云处理【三】(点云降采样)

点云降采样 第一章 点云数据采集 第二章 点云滤波 第二章 点云降采样 1. 为什么要降采样&#xff1f; 我们获得的数据量大&#xff0c;特别是几十万个以上的点云&#xff0c;里面有很多冗余数据&#xff0c;会导致处理起来比较耗时。 降采样是一种有效的减少数据、缩减计算量…

喜报 | 擎创科技实力亮相2023科创会并荣获科技创新奖

近日&#xff0c;由国家互联网数据中心产业技术创新战略联盟&#xff08;NIISA&#xff09;主办的“2023第二届国际互联网产业科技创新大会暨互联网创新产品展览会”于北京圆满落幕。 擎创科技副总裁冯陈湧受邀出席本次论坛&#xff0c;并发表了“银行分布式核心智能运维体系思…

毫米波雷达模块技术革新:在自动驾驶汽车中的前沿应用

随着自动驾驶技术的快速发展&#xff0c;毫米波雷达模块的技术革新成为推动这一领域的关键因素之一。本文将深入研究毫米波雷达模块技术的最新进展&#xff0c;并探讨其在自动驾驶汽车中的前沿应用。 毫米波雷达模块的基本原理 解释毫米波雷达模块的基本工作原理&#xff0c;强…

Goby 漏洞发布|Honeywell PM43 loadfile.lp 文件命令执行漏洞(CVE-2023-3710)

漏洞名称&#xff1a;Honeywell PM43 loadfile.lp 文件命令执行漏洞&#xff08;CVE-2023-3710&#xff09; English Name&#xff1a;Honeywell PM43 loadfile.lp file command execution vulnerability (CVE-2023-3710) CVSS core: 9.8 影响资产数&#xff1a; 96 漏洞描…

等保三级测评(MySQL)

mysql版本号:5.7.x 进入Mysql 通过mysql -u root -p 进入MySQL命令行,开展数据库配置核查; a)应对登录的用户进行身份标识和鉴别,身份标识具有唯一性,身份鉴别信息具有复杂度要求并定期更换; SELECT Host,User,authentication_string,plugin FROM mysql.user;b)被测…

软件测试(一)概念

软件测试 软件测试的生命周期&#xff1a; 需求分析→测试计划→ 测试设计、测试开发→ 测试执行→ 测试评估 需求分析&#xff1a;需求是非完整&#xff0c;需求是否正确测试计划&#xff1a;确定软件由谁测试&#xff0c;什么时候开始&#xff0c;什么时候结束&#xff0c;…

内网和热点同时连接使用配置

解决如标题问题 查看当前永久路由信息 route print截图保存(重要) 截图保存(重要)查出来的永久路由&#xff0c;以防配置不成功时回退&#xff0c;回退方法就是下面的“添加永久路由” 删除当前的路由 0.0.0.0 是上面查出的网络地址 route delete 0.0.0.0内网IP信息 添加永久…