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) 的空间开销,但在面试和日常解题中,这是最容易被理解和书写的版本。

相关推荐
凯瑟琳.奥古斯特2 小时前
力扣1235:加权区间调度最优解
java·python·算法·leetcode·职场和发展
耶叶2 小时前
餐厅出入最少人数问题:贪心算法
算法·贪心算法
gihigo19982 小时前
基于小波框架与稀疏表示的SAR图像目标识别系统(MATLAB实现)
算法
吴可可1232 小时前
CAD2004自定义实体开发环境配置
c++·算法
装不满的克莱因瓶2 小时前
矩阵的主成分是什么?主成分分析(PCA)又能做什么?
人工智能·线性代数·算法·机器学习·ai·矩阵·pca
大菜菜小个子2 小时前
template<typename T>使用
java·开发语言·算法
Fanfanaas3 小时前
C++ 继承
java·开发语言·jvm·c++·学习·算法
lqqjuly3 小时前
模型合并与融合:理论、算法与可运行实现—从损失曲面几何到多模型融合
算法
memcpy03 小时前
LeetCode 2144. 打折购买糖果的最小开销【贪心】
算法·leetcode·职场和发展
ID_180079054734 小时前
淘宝商品详情数据接口深度解析:架构、鉴权、数据结构与实战
数据结构·架构