当前位置:首页>python>说实话,LetCode有手就行!Python | 算法设计与分析(03)| 归并排序复刻:链表排序LCR 077. 排序链表一题吃透分治

说实话,LetCode有手就行!Python | 算法设计与分析(03)| 归并排序复刻:链表排序LCR 077. 排序链表一题吃透分治

  • 2026-09-08 00:05:58
说实话,LetCode有手就行!Python | 算法设计与分析(03)| 归并排序复刻:链表排序LCR 077. 排序链表一题吃透分治

很多同学一看到“排序链表”,第一反应是:

  • 先转数组

  • 排完再重建链表

小华学长也是,但能不能做?肯定是能的。但这题真正想考的,不是你会不会调库,而是你会不会把 归并排序 + 分治思想 用到链表上。

这也是算法课里非常典型的一类题:
数组适合随机访问,链表适合拆分与合并。


题目重述:

给定链表的头结点 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 = []输出:[]

这题为什么用归并排序?

因为链表不适合快速排序那种“按下标交换”的操作,但非常适合归并排序的两步:

  1. 从中间断开,分成左右两半

  2. 递归排序后合并

所以整题思路可以概括成一句话:

先分,再排,最后合。


核心思路:

第一步:快慢指针找中点

用 slow 和 fast 找链表中点:

  • slow 一次走一步

  • fast 一次走两步

当 fast 走到末尾时,slow 基本就在中间。

然后把链表从中间断开:

mid = slow.nextslow.next = None

第二步:递归排序左右两部分

对左半链表、右半链表分别递归调用排序函数。


第三步:合并两个有序链表

这一步和“合并两个有序链表”是同一个模板题。谁小接谁,最后把剩余部分接上去即可。


Python 实现:

# Definition for singly-linked list.class ListNode:    def __init__(self, val=0, next=None):        self.val = val        self.next = nextclass Solution:    def sortList(self, head):        # 递归终止:空链表 or 只有一个节点        if not head or not head.next:            return head        # 1. 快慢指针找中点        slow, fast = head, head        while fast.next and fast.next.next:            slow = slow.next            fast = fast.next.next        # 2. 从中间断开        mid = slow.next        slow.next = None        # 3. 递归排序左右两部分        left = self.sortList(head)        right = self.sortList(mid)        # 4. 合并两个有序链表        return self.merge(left, right)    def merge(self, l1, l2):        dummy = ListNode()        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


为什么这段代码是对的?

因为它完整符合归并排序的结构,只要 merge 是对的,整个递归结构就是成立的:

  • 分:找到中点,把链表拆成两半

  • 治:递归排序左边和右边

  • 合:把两个有序链表合并成一个有序链表


复杂度分析

(1)时间复杂度为O(nlogn)

原因:

1.每一层递归都会遍历并合并所有节点一次,代价是 O(n)

2.递归层数大约是 log n

所以,总复杂度是:O(n log n)

(2)空间复杂度也为O(nlogn)

严格说递归会占用栈空间,所以是 O(log n)。

这题最容易错的3个点

1. 忘记断链

这一句不能漏,如果不断开,递归会一直连着原链表,最后很容易死循环:

slow.next = None

2. 中点找错导致无法收缩

循环条件建议写成下面这样,这样 slow 会停在前半段尾部,便于断开:

while fast.next and fast.next.next:

3. merge 时指针推进顺序写乱

接完节点后,记得这样写,否则链表不会正确向后连接:

cur = cur.next

这题表面上是在排链表,本质上考的是:

你会不会把归并排序写到链表结构里。

所以真正该记住的不是“这题答案”,而是这套套路:

  • 快慢指针找中点

  • 断链

  • 递归排序

  • merge 合并

以后只要遇到 链表排序、链表分治、链表有序合并,都能直接套。很多同学第一次写这题,错的都不是思路,而是断链和边界。

小华学长这里留下一个问题:

为什么链表排序更适合归并,而不是快排?

想明白这个问题,这题就真的吃透了。

guan注小华学长,获取下期预告:

Python | 算法设计与分析(04)| 逆序对实战:归并排序不只是排序,还能边排边统计

最新文章

随机文章