【力扣100题】19. 排序链表 | 归并排序详解

一、题目描述

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

二、解题思路总览

核心思想:归并排序(自顶向下)

步骤 说明
分割 用快慢指针找到链表中点,将链表分成两半
递归 对左右两半分别递归排序
合并 将两个有序链表合并成一个有序链表

时间复杂度:O(n log n)

空间复杂度:O(log n)(递归栈)


三、完整代码

cpp 复制代码
class Solution {
public:
    // 1. 找到链表的中间节点,并断开
    ListNode* middleNode(ListNode* head) {
        ListNode* pre = head;
        ListNode* fast = head;
        ListNode* slow = head;

        while (fast && fast->next) {
            pre = slow;
            slow = slow->next;
            fast = fast->next->next;
        }

        pre->next = NULL;  // 从中间断开
        return slow;       // 返回后半段的头节点
    }

    // 2. 合并两个有序链表
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        ListNode* dummy = new ListNode(0);
        ListNode* cur = dummy;

        while (list1 && list2) {
            if (list1->val > list2->val) {
                cur->next = list2;
                list2 = list2->next;
            } else {
                cur->next = list1;
                list1 = list1->next;
            }
            cur = cur->next;
        }

        cur->next = list1 ? list1 : list2;
        return dummy->next;
    }

    // 3. 归并排序主函数
    ListNode* sortList(ListNode* head) {
        if (head == NULL || head->next == NULL) return head;

        // 找到中点,分割成两段
        ListNode* head2 = middleNode(head);

        // 递归排序左右两段
        head = sortList(head);
        head2 = sortList(head2);

        // 合并两个有序链表
        return mergeTwoLists(head, head2);
    }
};

四、算法流程图

4.1 sortList 主函数流程

复制代码
输入:链表头节点 head

[Step 1] 判断边界
        |
        v
    head == NULL 或 head->next == NULL ?
        |是                    |否
        v                     v
    返回 head             [Step 2] 继续
        |                     |
        v                     v
    【返回】            [Step 2] 找中点
                              |
                              v
                        head2 = middleNode(head)
                              |
                              v
                        链表被分成两段:
                        前半段 head,后半段 head2
                              |
                              v
                        【递归排序前半段】
                              |
                              v
                        head = sortList(head)
                              |
                              v
                        【递归排序后半段】
                              |
                              v
                        head2 = sortList(head2)
                              |
                              v
                        【合并两段有序链表】
                              |
                              v
                        return mergeTwoLists(head, head2)
                              |
                              v
                          【返回】

4.2 middleNode 找中点流程

复制代码
输入:链表头节点 head

[Step 1] 初始化指针
        pre = head, fast = head, slow = head
        |
        v
[Step 2] 快慢指针循环
        |
        v
    fast && fast->next 成立?
        |否                  |是
        v                   v
    【返回 slow】      [Step 3] 移动指针
        |                   pre = slow
                              slow = slow->next
                              fast = fast->next->next
        |                   |
        v                   v
                          回到 Step 2

4.3 mergeTwoLists 合并流程

复制代码
输入:两个有序链表 list1, list2

[Step 1] 创建哑节点
        dummy = new Node(0)
        cur = dummy
        |
        v
[Step 2] 比较接入循环
        |
        v
    list1 && list2 成立?
        |否                  |是
        v                   v
    [Step 4]           [Step 3] 比较大小
        |                   |
        v                   v
                  list1->val > list2->val ?
                      |是            |否
                      v             v
              cur->next = list2  cur->next = list1
              list2 = list2->next  list1 = list1->next
                      |             |
                      v             v
                  cur = cur->next
                      |
                      v
                  回到 Step 2
[Step 4] 处理剩余节点
        |
        v
    cur->next = list1 ? list1 : list2
        |
        v
    return dummy->next
        |
        v
    【返回合并后的链表】

4.4 整体递归展开流程

复制代码
sortList([4,2,1,3])
  |
  +-- middleNode --> 分割为 [4,2] 和 [1,3]
  |
  +-- sortList([4,2])
  |     |
  |     +-- middleNode --> 分割为 [4] 和 [2]
  |     |
  |     +-- sortList([4]) --> 返回 [4]
  |     |
  |     +-- sortList([2]) --> 返回 [2]
  |     |
  |     +-- merge([4], [2]) --> [2,4]
  |     |
  |     返回 [2,4]
  |
  +-- sortList([1,3])
  |     |
  |     +-- middleNode --> 分割为 [1] 和 [3]
  |     |
  |     +-- sortList([1]) --> 返回 [1]
  |     |
  |     +-- sortList([3]) --> 返回 [3]
  |     |
  |     +-- merge([1], [3]) --> [1,3]
  |     |
  |     返回 [1,3]
  |
  +-- merge([2,4], [1,3]) --> [1,2,3,4]
  |
  返回 [1,2,3,4]

五、逐行解析

5.1 middleNode:找中点并分割

原理:

  • slow 每次走一步
  • fast 每次走两步
  • 当 fast 到达末尾时,slow 正好在中间

循环条件: while (fast && fast->next)

变量 作用
slow 记录中点位置
fast 快指针,用来判断是否到末尾
pre 记录 slow 的前一个节点,用于断开

关键: pre->next = NULL 将链表从中点断开,分成两段。

举例:

复制代码
链表: 1 -> 2 -> 3 -> 4 -> 5 -> NULL

slow 走到: 3
pre  走到: 2
pre->next = NULL

前半段: 1 -> 2 -> NULL
后半段: 3 -> 4 -> 5 -> NULL

5.2 mergeTwoLists:合并两个有序链表

原理:

  • 创建 dummy 哑节点,简化边界处理
  • 用 cur 遍历,比较两个链表的当前节点
  • 小的节点接入新链表
  • 最后把剩余的部分直接接上去

循环逻辑:

复制代码
while (list1 && list2):
    if list1->val > list2->val:
        cur->next = list2
        list2 = list2->next
    else:
        cur->next = list1
        list1 = list1->next
    cur = cur->next

收尾: cur->next = list1 ? list1 : list2

如果 list1 还有剩余,就接 list1;否则接 list2(两者必居其一)


5.3 sortList:归并排序主逻辑

递归终止条件:

if (head == NULL || head->next == NULL) return head

空链表或单节点,直接返回(已经有序)


六、复杂度分析

指标 复杂度 说明
时间复杂度 O(n log n) 递归树高度 log n,每层共 O(n)
空间复杂度 O(log n) 递归栈深度 log n

时间复杂度推导:

复制代码
递归层数 = log n(每次对半分割)
每层总工作量 = O(n)(遍历所有节点)
总时间 = O(n log n)

七、面试追问

问题 回答要点
归并排序的时间复杂度是多少? O(n log n),每层分割都要遍历所有节点,递归深度 log n
为什么用快慢指针找中点? fast 速度是 slow 的 2 倍,fast 到末尾时 slow 正好在中点
pre->next = NULL 为什么要断开? 不断开的话,左右两段仍然是连在一起的,无法独立排序
合并时为什么要用 dummy 哑节点? 省去对头节点的特殊判断,统一用 cur->next 接入
递归栈会爆吗? n 不超过 10^5,递归深度 log n 不超过 17,完全安全
能用迭代实现吗? 可以用自底向上的归并排序,但代码更复杂
链表排序和数组排序有什么区别? 数组可以随机访问,可以用原地分割;链表必须靠指针遍历,分割需要找中点

八、相关题目

题号 题目 关键点
21 合并两个有序链表 mergeTwoLists 基础版
23 合并 K 个有序链表 堆优化或分治
148 排序链表 本题
143 重排链表 先找中点再合并
234 回文链表 快慢指针找中点

九、总结

要点 内容
核心算法 归并排序(自顶向下)
找中点 快慢指针,pre->next = NULL 断开
合并 双指针遍历,dummy 哑节点简化
复杂度 时间 O(n log n),空间 O(log n)
记忆口诀 分割中点、递归排序、合并链表三步走

相关推荐
ym_xixi1 小时前
《类和对象》—— 构造函数与析构函数总结
前端·c++·算法
洛水水1 小时前
【力扣100题】21. LRU 缓存
spring·leetcode·缓存
凯瑟琳.奥古斯特1 小时前
丑数II C++三指针解法(力扣264)
数据结构·c++·算法·leetcode·职场和发展
j_xxx404_1 小时前
力扣算法:用栈消消乐,巧解相邻重复与退格字符串
c++·算法·leetcode
Hello.Reader1 小时前
算法基础(十一)—— 递归树如何看懂分治算法的运行时间
java·算法·排序算法
郝学胜-神的一滴1 小时前
二叉树与递归:解锁高级数据结构的编程内功心法
开发语言·数据结构·c++·算法·面试
csdn_aspnet1 小时前
C++ (Naive Partition Algorithm)朴素划分算法
数据结构·c++·算法
eggrall1 小时前
找到字符串中所有字母异位词(medium)
算法·leetcode·职场和发展
_日拱一卒2 小时前
LeetCode:230二叉搜索树中第K小的元素
算法