LeetCode 148. 排序链表 —— 解法一:自顶向下递归(分治 + 归并)

LeetCode 148. 排序链表 ------ 解法一:自顶向下递归(分治 + 归并)

📌 题目链接

https://leetcode.cn/problems/sort-list/


💡 解题思路

要在链表上实现 O ( n log ⁡ n ) O(n \log n) O(nlogn) 的时间复杂度,归并排序(Merge Sort)是最佳选择。本题采用的是 自顶向下(Top-Down) 的递归策略。

核心思想可以概括为"分而治之":

  1. 分(Divide):利用快慢指针找到链表的中点,将链表从中间断开,递归地对左右两个子链表进行排序。
  2. 治(Conquer):当子链表被拆分到只剩一个节点(或为空)时,它天然就是有序的,此时开始返回。
  3. 合(Merge):将两个排好序的子链表合并成一个新的有序链表。

🛠 关键前置知识

为了更好地理解本题解,你需要掌握以下两道经典题目:

  • 876. 链表的中间结点

    • 作用:用于"分"。
    • 核心技巧:快慢指针(Fast & Slow Pointers)。快指针一次走两步,慢指针一次走一步,当快指针到达终点时,慢指针正好位于中点。
    • 关键点 :为了切断链表,本题解中额外引入了 pre 指针记录慢指针的前驱,以便执行 pre.next = null
  • 21. 合并两个有序链表

    • 作用:用于"合"。
    • 核心技巧:双指针遍历。创建一个虚拟头结点(Dummy Node),比较两个链表的头结点值大小,依次接入较小值,最后接上剩余的链表。

🧩 代码实现(Java)

java 复制代码
/**
 * Definition for singly-linked list.
 */
public class ListNode {
    int val;
    ListNode next;
    ListNode() {}
    ListNode(int val) { this.val = val; }
    ListNode(int val, ListNode next) { this.val = val; this.next = next; }
}

class Solution {  // 解法一:自顶向下递归
    public ListNode sortList(ListNode head) {
        // 1. 递归终止条件:空节点或单节点天然有序
        if (head == null || head.next == null) {
            return head;
        }

        // 2. 找到中点,分割链表
        ListNode head1 = middle(head); // head1 为右半部分的头结点

        // 3. 递归排序左右两部分
        head = sortList(head);
        head1 = sortList(head1);

        // 4. 合并两个有序链表
        return merge(head, head1);
    }

    /**
     * 876. 链表的中间结点(变体:要求断开链表)
     * 使用快慢指针找到中点,并切断连接
     */
    private ListNode middle(ListNode head) {
        ListNode pre = head;      // 记录慢指针的前一个节点
        ListNode slow = head;
        ListNode fast = head;

        while (fast != null && fast.next != null) {
            pre = slow;
            slow = slow.next;
            fast = fast.next.next;
        }

        // 断开链表:pre 是左半部分的尾,slow 是右半部分的头
        pre.next = null; 
        return slow;
    }

    /**
     * 21. 合并两个有序链表
     * 标准的双指针合并逻辑
     */
    private ListNode merge(ListNode l1, ListNode l2) {
        ListNode dummy = new ListNode(); // 虚拟头结点
        ListNode cur = dummy;

        while (l1 != null && l2 != null) {
            if (l1.val < l2.val) {
                cur.next = l1;
                l1 = l1.next;
            } else {
                cur.next = l2;
                l2 = l2.next;
            }
            cur = cur.next;
        }

        // 直接拼接剩余部分
        cur.next = (l1 != null) ? l1 : l2;
        return dummy.next;
    }
}

⏱ 复杂度分析

指标 复杂度 说明
时间复杂度 O ( n log ⁡ n ) O(n \log n) O(nlogn) 递归树的深度为 log ⁡ n \log n logn,每一层都需要遍历 n n n 个节点进行合并。
空间复杂度 O ( log ⁡ n ) O(\log n) O(logn) 主要由递归调用栈产生(递归深度为 log ⁡ n \log n logn)。

✨ 总结

这种自顶向下 的解法逻辑非常清晰,完美复用了"找中点"和"合并"这两个基础操作。虽然递归栈带来了 O ( log ⁡ n ) O(\log n) O(logn) 的空间开销,但在面试和日常解题中,这是最容易被理解和书写的版本。

相关推荐
复杂网络3 小时前
论最小 Agent 计算机的形态
算法
kisshyshy18 小时前
🍦 雪糕、食堂、火车厢:三幅漫画吃透栈、队列与链表
javascript·算法
猿人谷1 天前
不只是 CPU 阈值:STAR 如何用 GAT + Transformer 做容器级自动扩缩容?
人工智能·算法
复杂网络1 天前
Stable Diffusion 视觉大模型微调技术深度调研
算法
复杂网络1 天前
基于 Stable Diffusion 架构的视觉大模型代表性工作与原理深度解析
算法
MrZhao4001 天前
Agent Loop 如何用 Hook 扩展:权限、日志与工具拦截
算法
MrZhao4001 天前
Agent 为什么需要 Skills:别把所有知识都塞进 system prompt
算法
JieE2123 天前
LeetCode 101. 对称二叉树|JS 递归 + 迭代双解法,彻底搞懂镜像判断
javascript·算法
JieE2124 天前
LeetCode 56. 合并区间|超清晰 JS 图解思路,面试高频区间题
javascript·算法·面试
Jack204 天前
HarmonyOS开发中错误处理策略:网络异常统一处理
算法