当前位置: 首页 > news >正文

算法148. 排序链表

题目:

给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。

示例 1:
在这里插入图片描述
输入:head = [4,2,1,3]
输出:[1,2,3,4]
示例 2:
在这里插入图片描述

输入:head = [-1,5,3,4,0]
输出:[-1,0,3,4,5]

示例 3:
输入:head = []
输出:[]

解法:

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:# 链表的中间节点(快慢指针法)def middleNode(self,head:Optional[ListNode])->Optional[ListNode]:if not head:return Noneslow=headfast=headwhile fast and fast.next:pre = slowslow=slow.nextfast=fast.next.nextpre.next = None # 断开slow的前一个节点和slow节点,拆分成两个链表return slow# 合并两个有序链表(双指针)def mergeTwoLists(self,list1:Optional[ListNode],list2:Optional[ListNode])->Optional[ListNode]:# 用哑节点标记链表头节点cur = dummy = ListNode()# 遍历两个有序链表,直到其中一个遍历完while list1 and list2:if list1.val >= list2.val:cur.next=list2list2=list2.nextelse:cur.next=list1list1=list1.nextcur=cur.next# 将没有遍历完的拼接到最后cur.next=list1 if list1 else list2return dummy.next# 排序链表def sortList(self,head:Optional[ListNode])->Optional[ListNode]:# 如果链表为空或者只有一个节点,无需排序if head is None or head.next is None:return head# 找到中间节点head2,并断开head2与其前一个节点的联接head2 = self.middleNode(head)# 分治,比如把[4,2,1,3]拆成[4,2]和[1,3],然后把[4,2]拆成[4][2],[1,3]拆成[1][3]head = self.sortList(head)head2 = self.sortList(head2)#合并return self.mergeTwoLists(head,head2)

解析:

这段代码是链表的归并排序(Merge Sort)实现,非常经典!我们来逐行讲解,重点剖析 sortList 中的递归逻辑,以及整个流程是如何运作的。


🌟 总体结构

这是一个典型的 分治算法(Divide and Conquer)

  1. :用快慢指针找到链表中点,把链表从中间断开成两部分。
  2. :对左右两部分分别递归排序。
  3. :将两个有序链表合并成一个有序链表(调用 mergeTwoLists)。

最终返回排好序的链表头。


✅ 三个函数的作用

1. middleNode(head):找链表中点并断开前连接

def middleNode(self, head: Optional[ListNode]) -> Optional[ListNode]:slow = fast = headwhile fast and fast.next:pre = slow      # 记录 slow 的前一个节点slow = slow.nextfast = fast.next.nextpre.next = None     # 断开连接return slow         # 返回中间节点作为右半部分的头
🔍 作用:
  • 找到链表的中间节点(偏右的那个,比如偶数长度时返回第 2 个)
  • 并且 断开左半部分和右半部分的连接
📌 举个例子:

输入:4 -> 2 -> 1 -> 3

执行过程:

slow 和 fast 从 head=4 开始第1步:slow=4, pre=Noneslow=2, fast=1第2步:pre=2, slow=1, fast=None → 结束pre.next = None → 2.next = None

结果:

  • 原链表被断成:
    • 左半:4 -> 2
    • 返回 slow=1,即右半:1 -> 3

✅ 所以这个函数不仅“找中点”,还完成了“分割”任务!

⚠️ 注意:pre 是在循环中动态记录 slow 的前驱,这样才能断开。


2. mergeTwoLists(list1, list2):合并两个有序链表

标准写法,使用哨兵节点:

def mergeTwoLists(self, list1, list2) -> ListNode:cur = dummy = ListNode()while list1 and list2:if list1.val < list2.val:cur.next = list1list1 = list1.nextelse:cur.next = list2list2 = list2.nextcur = cur.nextcur.next = list1 or list2  # 接上剩余部分return dummy.next

✅ 这个函数保证:给两个有序链表,返回一个合并后的有序链表。


3. sortList(head):主函数,递归实现归并排序

def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]:# 终止条件:空或只有一个节点if head is None or head.next is None:return head# 分:找到中点,并断开head2 = self.middleNode(head)# 治:递归排序左右两部分head = self.sortList(head)      # 排序左半head2 = self.sortList(head2)    # 排序右半# 合:合并两个有序链表return self.mergeTwoLists(head, head2)

🧠 重点:sortList 的递归是怎么运行的?

我们用一个例子来 trace:4 -> 2 -> 1 -> 3

🌲 第一层递归(原始调用)

head = 4 -> 2 -> 1 -> 3
  1. 调用 middleNode(head)

    • 返回 head2 = 1 -> 3
    • 原链表变成:4 -> 2(因为 pre.next = None
  2. 递归调用:

    head = sortList(4 -> 2)
    head2 = sortList(1 -> 3)
    

🔁 第二层递归:sortList(4 -> 2)

head = 4 -> 2
  1. middleNode(4 -> 2)

    • slow 会走到 2pre = 4
    • pre.next = None4.next = None
    • 返回 head2 = 2
  2. 现在:

    • head = 4(单节点)
    • head2 = 2(单节点)
  3. 递归调用:

    sortList(4) → 返回 4
    sortList(2) → 返回 2
    
  4. 合并:mergeTwoLists(4, 2) → 返回 2 -> 4

✅ 所以 sortList(4 -> 2) 返回 2 -> 4


🔁 第二层递归:sortList(1 -> 3)

类似地:

  1. middleNode(1 -> 3)

    • 返回 head2 = 3,原链表断开为 13
  2. 递归:

    • sortList(1) → 返回 1
    • sortList(3) → 返回 3
  3. 合并:mergeTwoLists(1, 3) → 返回 1 -> 3

✅ 所以 sortList(1 -> 3) 返回 1 -> 3


🔚 第一层合并

现在回到第一层:

head = 2 -> 4
head2 = 1 -> 3

调用:

mergeTwoLists(2->4, 1->3)

合并过程:

  • 比较 2 和 1 → 取 1
  • 比较 2 和 3 → 取 2
  • 比较 4 和 3 → 取 3
  • 最后接上 4

结果:1 -> 2 -> 3 -> 4

✅ 完美排序!


📌 递归树可视化

                 [4,2,1,3]/         \[4,2]           [1,3]/    \           /    \[4]    [2]       [1]    [3]\     /          \     /merge(2,4)       merge(1,3)\             /merge(2->4, 1->3)|[1->2->3->4]

🧩 关键点解析

1. 为什么 middleNode 要返回 slow 并断开 pre.next

  • slow 是右半部分的头
  • pre.next = None 是为了断开左右两部分,避免递归时无限循环
  • 否则 sortList(head) 还是整个链表,无法真正“分”

2. 递归终止条件是什么?

if head is None or head.next is None:return head
  • 空链表或单节点 → 已经有序 → 直接返回

3. 为什么可以递归调用 sortList(head)

因为 middleNode 已经把 head 所在的链表从中间断开了,现在 head 指向的是左半部分,长度减半,递归下去一定会到达终止条件。

4. 时间复杂度?

  • 每层 O(n) 合并
  • 递归深度 O(log n)
  • 总体:O(n log n)

5. 空间复杂度?

  • 递归栈深度 O(log n)
  • 链表本身不额外分配节点
  • 所以是 O(log n)(递归栈空间)

✅ 总结

步骤函数作用
middleNode找中点,断开链表
sortList 递归对左右两部分分别排序
mergeTwoLists合并两个有序链表

💡 小技巧记忆

链表归并排序 = 快慢找中 + 断开 + 递归 + 合并

就像切面条:

  1. 一刀从中间切开(middleNode
  2. 左右分别再切,直到变成一根(递归到底)
  3. 然后从小到大一根根合并回去(merge

http://www.dtcms.com/a/330653.html

相关文章:

  • 希尔排序专栏
  • docker compose部署mysql
  • UniVoc:基于二维矩阵映射的多语言词汇表系统
  • ESP32单片机与STM32单片机协同完成工业控制智能化改造
  • Uniapp 条件编译详解
  • 用Python从零开始实现机器学习Baseline
  • 文件服务器:FTP
  • Inconsistent vendoring detected. Please re-run “go mod vendor“.
  • QML开发:画布元素
  • 数学建模-非线性规划模型
  • Spring AI PagePdfDocumentReader 全解析:基于 Apache PdfBox 的按页 PDF 读取实战
  • PDF压缩原理详解:如何在不失真的前提下减小文件体积?
  • 高分辨率PDF压缩技巧:保留可读性的最小体积方案
  • PDF 段落提取利器:Spring AI 的 ParagraphPdfDocumentReader 实战
  • 【办公自动化】如何使用Python操作PPT和自动化生成PPT?
  • pdf文件流或者本地文件读取
  • OSI七层模型和TCP/IP协议簇
  • elasticsearch冷热数据读写分离!
  • Qt TCP 客户端对象生命周期与连接断开问题解析
  • ESXI 6.7服务器时间错乱问题
  • 面试题之项目中git如何进行管理
  • 计算机如何进行“卷积”操作:从图像到矩阵的奥秘
  • 星空开源MES(万界星空科技)——专业、通用、开源、免费的MES系统
  • AM32电调学习-使用Keil编译uboot
  • 医疗AI问答系统实战:知识图谱+大模型的融合应用开发
  • 带环链表详解:环形链表检测与入环节点查找
  • 推荐三个国内开源数据治理工具
  • Python3 详解:从基础到进阶的完整指南
  • 第四天~什么是ARXML?
  • CV 医学影像分类、分割、目标检测,之【肺结节目标检测】项目拆解