LeetCode 148:排序链表 (Sort Linked List)

news/2025/3/5 0:25:04/

题目描述:
给定一个单链表 head,将其按升序排序并返回排序后的链表

  • 输入条件: 链表长度不固定(可为空)。
  • 需要在O(n log n)时间复杂度和O(1)空间复杂度下完成 原地排序(特别限制)。

题解与思路分析

排序链表的经典解法通常围绕归并排序,因为归并排序天然适合链表场景,具有以下特点:

  1. 链表无法随机访问: 链表适合用归并排序,而不适用快排(链表快排虽然实现复杂,但可以尝试)。
  2. 时间复杂度要求 O(n log n)
    归并排序在时间复杂度上满足要求;另一种选择是基于堆的优先队列排序。

以下是几种常见解法:


解法 1:归并排序(递归实现,分治法)

思路

归并排序依赖“分治”:

  1. 分割链表:通过快慢指针找到链表的中点,将链表分成左右两半。
  2. 递归排序左右子链表
  3. 合并两个有序链表:通过双指针合并两个递归排序后的链表

模板代码(递归归并)

class Solution {public ListNode sortList(ListNode head) {// 递归结束条件:链表为空或只有一个节点if (head == null || head.next == null) {return head;}// 1. 找链表中点,拆分链表ListNode mid = findMiddle(head);ListNode rightHead = mid.next;mid.next = null; // 断开链表// 2. 递归排序左右两部分ListNode left = sortList(head);ListNode right = sortList(rightHead);// 3. 合并两个有序链表return mergeTwoLists(left, right);}// 快慢指针找链表的中点(慢指针指向下中点)private ListNode findMiddle(ListNode head) {ListNode slow = head, fast = head;while (fast.next != null && fast.next.next != null) {slow = slow.next;fast = fast.next.next;}return slow;}// 合并两个有序链表private ListNode mergeTwoLists(ListNode l1, ListNode l2) {ListNode dummy = new ListNode(0);ListNode p = dummy;while (l1 != null && l2 != null) {if (l1.val < l2.val) {p.next = l1;l1 = l1.next;} else {p.next = l2;l2 = l2.next;}p = p.next;}if (l1 != null) p.next = l1;if (l2 != null) p.next = l2;return dummy.next;}
}

时间和空间复杂度:

  1. 时间复杂度:O(n log n)
    • 找中点需要 O(n),递归二分对数级叠加,总时间复杂度为 O(n log n)。
  2. 空间复杂度:O(log n)
    • 递归深度为 log n(由二分决定)。

解法 2:归并排序(迭代实现,自底向上)

思路

递归实现的归并排序需要调用栈导致 O(log n) 的空间复杂度,如果希望做到完全原地排序,可以使用迭代归并(Bottom-Up Merge Sort):

  1. 链表最小子链表开始,每次合并两个子链表,逐步扩大子链表长度直到覆盖整个链表
  2. 使用一个内部循环控制子链表长度,外部循环控制链表的分组。

模板代码(迭代归并)

class Solution {public ListNode sortList(ListNode head) {if (head == null || head.next == null) return head;// 1. 计算链表长度int length = 0;ListNode cur = head;while (cur != null) {length++;cur = cur.next;}// 2. Dummy 节点辅助处理ListNode dummy = new ListNode(0, head);// 3. 逐步扩大子链表长度 (size)for (int size = 1; size < length; size *= 2) {ListNode prev = dummy;  // 用于记录已处理部分尾节点cur = dummy.next;      // 当前未处理的链表头节点while (cur != null) {// 分割链表为 left 和 right 两部分ListNode left = cur;ListNode right = split(left, size); // 分割左链表,返回右链表的头cur = split(right, size);          // 分割右链表,返回下一部分的头// 合并两个有序链表prev.next = mergeTwoLists(left, right);// 移动到下一个部分末尾while (prev.next != null) {prev = prev.next;}}}return dummy.next;}// 按长度分割链表:截取链表前 size 个节点,返回剩余链表private ListNode split(ListNode head, int size) {if (head == null) return null;ListNode cur = head;for (int i = 1; i < size && cur.next != null; i++) {cur = cur.next;}ListNode next = cur.next;cur.next = null; // 分割return next;}// 合并两个有序链表(同递归实现)private ListNode mergeTwoLists(ListNode l1, ListNode l2) {ListNode dummy = new ListNode(0);ListNode p = dummy;while (l1 != null && l2 != null) {if (l1.val < l2.val) {p.next = l1;l1 = l1.next;} else {p.next = l2;l2 = l2.next;}p = p.next;}if (l1 != null) p.next = l1;if (l2 != null) p.next = l2;return dummy.next;}
}

时间和空间复杂度:

  1. 时间复杂度:O(n log n)
    • 外循环处理 log n 层,每层最多 O(n)。
  2. 空间复杂度:O(1)
    • 这种方法没有递归,因此是真正的原地排序。

解法 3:链表快速排序

思路

快速排序对链表不如数组好用,因为无法快速定位中点。实现上每次需要选取一个分区点 (pivot),然后将链表分割为小于 pivot 和大于 pivot 的两部分,再递归排序。

  1. 使用链表拆分:扫描每个节点,分为 lowhigh 列表。
  2. 递归排序 lowhigh 后,拼接到 pivot

模板实现

class Solution {public ListNode sortList(ListNode head) {if (head == null || head.next == null) return head;// 选择第一个节点作为 pivotListNode pivot = head;ListNode lowHead = new ListNode(0), highHead = new ListNode(0);ListNode low = lowHead, high = highHead;// 拆分链表ListNode cur = head.next;while (cur != null) {if (cur.val < pivot.val) {low.next = cur;low = low.next;} else {high.next = cur;high = high.next;}cur = cur.next;}low.next = null;high.next = null;// 递归排序 low 和 highListNode sortedLow = sortList(lowHead.next);ListNode sortedHigh = sortList(highHead.next);// 拼接:low -> pivot -> highreturn concat(sortedLow, pivot, sortedHigh);}private ListNode concat(ListNode low, ListNode pivot, ListNode high) {ListNode dummy = new ListNode(0), cur = dummy;cur.next = low;while (cur.next != null) cur = cur.next;cur.next = pivot;pivot.next = high;return dummy.next;}
}

时间和空间复杂度:

  1. 时间复杂度:O(n log n)
    • 平均效率较高,最差 O(n²)。
  2. 空间复杂度:O(log n)
    • 递归栈消耗。

经典变体题目

1. 合并 K 个有序链表(LeetCode 23)

解题方法:

  • 分治合并:不断划分后两两合并链表
  • 优先队列:用小顶堆每次选择最小值。

2. 排序奇数链表部分

问题:

  • 链表中仅奇数值的节点需要排序,而偶数节点保持原顺序。
  • 解法:拆分原链表,分别排序后再合并。

3. 重新排序链表

问题:

  • 链表重新排序:head -> tail -> nextHead -> nextTail …
  • 双指针中点拆分 + reverse 后半部分处理。

快速 AC 总结:

  1. 优先熟练掌握 递归归并排序,作为 LeetCode AC 的首选。
  2. 迭代归并排序在强调空间复杂度下非常重要。
  3. 拓展常见链表排序变形,熟悉归并/快排的变体问题应对跨题目场景!

http://www.ppmy.cn/news/1576681.html

相关文章

【动态规划学习】区间dp

区间dp概述 区间dp&#xff0c;就是在一段区间上进行动态规划&#xff0c;求解一段区间的最优解。最后合并小区间上的最优解从而得到全局最优解的算法。 【问题引入】 石子合并问题 N堆石子摆成一条线。现要将石子有次序地合并成一堆。规定每次只能选相邻的两堆石子合并成新的…

Ubuntu 下 nginx-1.24.0 源码分析 - ngx_init_cycle 函数 - 详解(4)

详解&#xff08;4&#xff09; 初始化配置转储结构&#xff08;config_dump&#xff09; if (ngx_array_init(&cycle->config_dump, pool, 1, sizeof(ngx_conf_dump_t))! NGX_OK){ngx_destroy_pool(pool);return NULL;}ngx_rbtree_init(&cycle->config_dump_rb…

第十三届蓝桥杯大赛软件赛决赛C/C++ 大学 B 组

A 【2022——暴力DP / 优雅背包】-CSDN博客 B 【钟表——类日期问题】-CSDN博客 C 【卡牌——二分】-CSDN博客 D 【最大数字——DFS】-CSDN博客 E 【出差——Dijkstra】-CSDN博客 F 【费用报销——01背包】-CSDN博客 G 【故障——条件概率】-CSDN博客 H 【机房—…

Oracle 数据库基础入门(四):分组与联表查询的深度探索(下)

在 Oracle 数据库的操作中&#xff0c;联合查询与子查询是获取复杂数据的关键手段。当单表数据无法满足业务需求时&#xff0c;联合查询允许我们从多张表中提取关联信息&#xff0c;而子查询则能以嵌套的方式实现更灵活的数据筛选。对于 Java 全栈开发者而言&#xff0c;掌握这…

服务异步通讯与RabbitMQ

服务异步通讯 文章目录 服务异步通讯MQRabbitMQ1、安装&#xff08;部署&#xff09;2、结构3、消息模型4、SpringAMQP4.1、基本消息队列4.2、工作消息队列4.3、发布订阅模型4.3.1、FanoutExchange&#xff08;广播类型的交换机&#xff09;4.3.2、DirectExchange&#xff08;路…

2月28日,三极管测量,水利-51单片机

众所周知&#xff0c;三极管&#xff08;BJT&#xff09;有三个管脚&#xff0c;基极&#xff08;B&#xff09;、集电极&#xff08;C&#xff09;、发射极&#xff08;E&#xff09;&#xff0c;在实际应用中&#xff0c;不可避免地会遇到引脚辨别的问题。接下来就讲下三极管…

哪些方法可以查看drupal版本

在 Drupal 中&#xff0c;你可以使用多种方式来查看当前的 Drupal 版本&#xff0c;以下是几种方法&#xff1a; 方法 1&#xff1a;通过管理后台查看&#xff08;适用于管理员&#xff09; 登录 Drupal 后台。进入 管理 → 报告 → 状态报告&#xff08;路径&#xff1a;/adm…

使用通义万相Wan2.1进行视频生成

使用通义万相Wan2.1进行视频生成 源代码准备运行环境准备创建Python虚拟环境并激活安装依赖包 模型下载生成视频官网的视频生成例子简单描述场景视频生成示例详细描述场景视频生成示例 最近通义万相开源了其视频生成模型。模型有两个版本&#xff0c;一个是1.3B的&#xff0c;一…