【Hot 100 刷题计划】 LeetCode 148. 排序链表 | C++ 归并排序自顶向下

LeetCode 148. 排序链表

📌 题目描述

题目级别:中等

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

进阶:

你可以在 O(Nlog⁡N)O(N \log N)O(NlogN) 时间复杂度和常数级空间复杂度下,对链表进行排序吗?

  • 示例 1:
    输入:head = [4,2,1,3]
    输出:[1,2,3,4]

💡 破题思路:归并排序 (Divide and Conquer)

要在 O(Nlog⁡N)O(N \log N)O(NlogN) 时间内完成排序,常见的算法有快速排序、堆排序和归并排序。

但在链表这种数据结构中,**归并排序(Merge Sort)**是绝对的王者。因为它可以在合并阶段直接通过修改指针来实现,不需要像数组那样开辟大量的临时拷贝空间。

本解法采用了**自顶向下(Top-down)**的递归归并排序,核心分为两大步:

1. 分(Divide):快慢指针找中点

利用经典的快慢指针法把链表从中间一分为二。
⚠️ 极客避坑点

找中点时,fast 指针必须初始化为 head->next

如果 fast 初始化为 head,在处理只有 2 个节点的链表时,slow 会走到第 2 个节点。此时从中点断开,左边依然是 2 个节点,会导致递归陷入死循环,最终爆栈。让 fast 先走一步,可以确保在偶数节点时,slow 准确停在左半部分的最后一个节点。

2. 合(Merge):双指针合并有序链表

这部分逻辑完全等同于经典的题目《21. 合并两个有序链表》。

创建一个虚拟头节点 dummy,然后比较左右两半链表的当前节点,谁小谁就接在 dummy 的后面,最后将剩余的尾巴接上即可。


💻 C++ 代码实现 (原汁原味作者版)

cpp 复制代码
class Solution {
public:
    ListNode* sortList(ListNode* head) {
        // 递归终止条件:如果链表为空,或只有一个节点,天然是有序的,直接返回
        if (!head || !head->next)
        {
            return head;
        }

        // 1. 快慢指针找中点
        ListNode *slow = head;
        // 重点细节:fast 先走一步,防止偶数节点时划分死循环
        ListNode *fast = head->next; 

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

        // 2. 将链表从 slow 处断开,分为 [head, slow] 和 [slow->next, 尾] 两部分
        ListNode *second = slow->next;
        slow->next = nullptr;
        ListNode *first = head;
        
        // 3. 对左右两半分别进行深度优先递归排序
        first = sortList(first);
        second = sortList(second);

        // 4. 将两个排序好的单链表合并
        return mergeList(first, second);
    }

    // 辅助函数:合并两个有序链表
    ListNode* mergeList(ListNode *left, ListNode *right)
    {
        ListNode *dummy = new ListNode(0);
        ListNode *cur = dummy;

        while (left && right)
        {
            if (left->val < right->val)
            {
                cur->next = left;
                left = left->next;
            }
            else 
            {
                cur->next = right;
                right = right->next;
            }
            cur = cur->next;
        }

        // 把未遍历完的剩余部分直接挂在末尾
        if (left) cur->next = left;
        else cur->next = right;

        return dummy->next;
    }
};
相关推荐
想吃火锅10056 分钟前
【leetcode】1.两数之和js版
javascript·算法·leetcode
qeen876 分钟前
【C++】类与对象之类的默认成员函数(二)
android·c语言·开发语言·c++·笔记·学习
王老师青少年编程1 小时前
信奥赛C++提高组csp-s之搜索进阶(记忆化搜索案例实践3)
c++·记忆化搜索·方格取数·csp·信奥赛·csp-s·提高组
Titan20242 小时前
Linux动静态库
linux·服务器·c++
j_xxx404_2 小时前
MySQL表操作硬核解析:从 CREATE TABLE 到磁盘文件、ALTER TABLE 与 DDL 风险
运维·服务器·数据库·c++·mysql·adb·ai
wuminyu3 小时前
Java锁机制之park和unpark源码剖析
java·linux·c语言·jvm·c++
玖玥拾3 小时前
C/C++ 基础笔记(十一)类的进阶
c语言·c++·设计模式·
-森屿安年-4 小时前
1137. 第 N 个泰波那契数
c++·动态规划
如何原谅奋力过但无声4 小时前
【灵神高频面试题合集09-13】二叉树、二叉搜索树
数据结构·算法·leetcode
程序员老舅4 小时前
从内核视角,看Linux文件读写过程
linux·服务器·c++·内核·linux内核·vfs·linux内存