【力扣-148. 排序链表】Python笔记

链表排序的"分治"魔法:归并排序的优雅应用


📚 核心知识点:归并排序与链表特性

因此,归并排序(Merge Sort) 成为了链表排序的最优解,因为它天生契合链表的特性:

  1. 分(Divide) :利用快慢指针找到链表中点,将链表一分为二。
  2. 治(Conquer) :递归地对左右两部分进行排序。
  3. 合(Merge) :利用双指针将两个有序链表合并为一个。

为什么是 O(n \\log n)?

  • 找中点需要遍历链表,耗时 O(n)。
  • 递归深度为 \\log n。
  • 总时间复杂度:O(n \\log n),且不需要额外空间(除了递归栈),非常高效。

📝 题目解析:LeetCode 148. 排序链表

题目描述 :

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

示例 :

输入:head = [4,2,1,3]

输出:[1,2,3,4]


💡 解题思路:递归归并的三部曲

这道题的核心在于"切分" 和 "合并"。我们可以把它想象成整理一副乱序的扑克牌:先把牌分成两堆,每堆再分两堆......直到每堆只有一张牌(自然有序),然后把它们两两合并,最终得到整副有序的牌。

步骤拆解:

  1. 终止条件:如果链表为空或只有一个节点,它已经是有序的了,直接返回。
  2. 寻找中点(切分) :使用快慢指针 。
    • 快指针 fast 每次走两步,慢指针 slow 每次走一步。
    • 当 fast 走到尽头,slow 正好在中点。
    • 关键一步 :我们需要切断 slow 前面的连接,将链表分为 [head...prev] 和 [slow...end] 两部分。
  3. 递归排序 :分别对左右两段链表调用 sortList。
  4. 合并链表 :调用 merge 函数,将两个有序链表合并成一个。

💻 代码实现(Python3)

代码如下:

python 复制代码
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next

class Solution:
    def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]:
        # 1. 递归终止条件:空节点 or 单个节点(已经有序)
        if not head or not head.next:
            return head

        # ===================== 步骤1:找中点并切断链表 =====================
        # 慢指针、快指针、前驱(用于切断)
        slow, fast, prev = head, head, None
        while fast and fast.next:
            prev = slow       # 保存慢指针的前一个节点
            slow = slow.next  # 慢指针走1步
            fast = fast.next.next # 快指针走2步

        # 此时:
        # prev 是左半段的尾
        # slow 是右半段的头
        prev.next = None # 关键:切断链表!否则左半段会连着右半段,导致死循环

        # ===================== 步骤2:递归排序左右两半 =====================
        left = self.sortList(head)  # 排序左半段
        right = self.sortList(slow) # 排序右半段

        # ===================== 步骤3:合并两个有序链表 =====================
        # 复用 21题 的合并逻辑
        return self.merge(left, right)

    # 辅助函数:合并两个有序链表(源自 LeetCode 21)
    def merge(self, l1: ListNode, l2: ListNode) -> ListNode:
        dummy = ListNode(0) # 虚拟头节点,简化操作
        cur = dummy

        # 双指针比较,谁小接谁
        while l1 and l2:
            if l1.val <= l2.val:
                cur.next = l1
                l1 = l1.next
            else:
                cur.next = l2
                l2 = l2.next
            cur = cur.next

        # 处理剩余节点
        cur.next = l1 if l1 else l2
        return dummy.next

🔍 深度解析:为什么要用 prev 切断链表?

在找中点的循环中,你可能会问: "为什么不只用 slow 和 fast,还要多一个 prev?"

原因 :

当我们找到中点 slow 时,链表依然是连通的:... -> prev -> slow -> ...。

如果不切断 prev.next,左半部分链表 head 的尾部依然指向右半部分。

  • 如果我们递归处理 head,它会包含整个右半部分,导致无限递归(栈溢出) 。
  • 所以,prev.next = None 这一步是灵魂操作,它确保了左半部分是一个独立的、被截断的链表。

📌 总结

这道题是链表操作的综合考察:

  1. 快慢指针找中点(分治的基础)。
  2. 递归思维(将大问题拆解为小问题)。
  3. 双指针合并(有序链表的经典操作)。

掌握了这道题,你对链表的理解绝对能上一个大台阶!加油,打工人!

相关推荐
丨白色风车丨18 分钟前
Python中JSON与字典/列表的相互转换详解
windows·python·json
梦想不只是梦与想20 分钟前
SQLAlchemy:查询与操作 (三)
数据库·python·sqlalchemy
计算机编程指导师24 分钟前
【大数据毕设】基于Hadoop+Django乳腺癌数据分析与可视化系统选题推荐 源码 毕业设计 毕设选题 数据分析 机器学习
大数据·hadoop·python·计算机·毕业设计·课程设计·乳腺癌
打工仔折腾 AI27 分钟前
鸿蒙版ToDesk远控实测:蓝牙键鼠、双模式鼠标与跨端剪贴板怎么用
后端·python·华为·性能优化·计算机外设·harmonyos·ai agent 实战
启观川28 分钟前
数据结构与算法 -第 3 章 常用算法-回溯算法
数据结构·笔记·python·算法
计算机编程指导师37 分钟前
Python毕设怎么做?基于hadoop的商家优惠券营销效果数据分析与可视化系统源码 毕业设计 选题推荐 毕设选题 数据分析 机器学习
大数据·hadoop·python·spark·毕业设计·课程设计·优惠券
计算机编程指导师42 分钟前
【Python毕设推荐】基于Hadoop+Spark的商场商铺运行数据分析与可视化系统源码 毕业设计 选题推荐 毕设选题 数据分析 机器学习
大数据·hadoop·python·计算机·spark·课程设计·商场商铺
haluhalu.1 小时前
Agent = LLM + 上下文 + 工具(含观察 / 动作空间、ReAct 循环)
前端·javascript·人工智能·python·react.js·机器学习·个人开发
mldong1 小时前
给 Django Admin 加审批流:一行前端代码没写,298 行接上工作流引擎
后端·python·架构
kobe_OKOK_8 小时前
__getattr__和__getattribute__如何用
python