LeetCode 148. 排序链表 —— 解法二:自底向上归并(迭代,O(1) 空间)

LeetCode 148. 排序链表 ------ 解法二:自底向上归并(迭代,O(1) 空间)

📌 题目链接

解法一,自顶向下


💡 解题思路

本题要求在 O(n log n) 时间内排序链表,且尽可能少用额外空间

解法一使用了递归,空间复杂度为 O(log n)

本解法采用 自底向上(Bottom-Up)归并排序 ,完全消除递归栈,实现 O(1) 额外空间

核心思想

不递归拆链,而是按固定步长合并子链表

  1. 先统计链表长度 len
  2. step = 1 开始:
    • 每次合并长度为 step 的两个子链表
    • 合并完成后,step *= 2
  3. step >= len 时,链表已完全有序

🛠 关键技术点

1️⃣ 获取链表长度

java 复制代码
int getLen(ListNode node)
  • 用于确定归并的上界
  • 时间复杂度 O(n)

2️⃣ 按长度切割链表

java 复制代码
ListNode splitList(ListNode head, int size)

功能:

  • head 向后走 size - 1
  • 将链表切断
  • 返回下一段的头结点

📌 若剩余不足 size,直接返回 null


3️⃣ 合并两个有序链表(返回头 + 尾)

java 复制代码
ListNode[] merge(ListNode l1, ListNode l2)

不同于传统只返回头结点,这里返回:

text 复制代码
[合并后链表的头, 合并后链表的尾]

原因:

  • 自底向上需要不断接在 newListTail 后面
  • 知道尾指针可以避免再次遍历

🧩 完整代码(Java)

java 复制代码
class Solution {  // 解法二:自底向上归并排序(迭代)
    public ListNode sortList(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }

        int len = getLen(head);
        ListNode dummy = new ListNode(0, head);

        // step: 当前每个子链表的长度
        for (int step = 1; step < len; step *= 2) {
            ListNode newListTail = dummy;
            ListNode cur = dummy.next;

            while (cur != null) {
                ListNode head1 = cur;
                ListNode head2 = splitList(head1, step);
                cur = splitList(head2, step);

                ListNode[] merged = merge(head1, head2);
                newListTail.next = merged[0];
                newListTail = merged[1];
            }
        }
        return dummy.next;
    }

    // 获取链表长度
    int getLen(ListNode node) {
        int cnt = 0;
        while (node != null) {
            cnt++;
            node = node.next;
        }
        return cnt;
    }

    // 按长度切分链表
    ListNode splitList(ListNode head, int size) {
        if (head == null) return null;

        ListNode cur = head;
        for (int i = 0; i < size - 1 && cur != null; i++) {
            cur = cur.next;
        }

        if (cur == null || cur.next == null) {
            return null;
        }

        ListNode nextHead = cur.next;
        cur.next = null;
        return nextHead;
    }

    // 合并两个有序链表,返回 [头, 尾]
    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;

        while (cur.next != null) {
            cur = cur.next;
        }
        return new ListNode[]{dummy.next, cur};
    }
}

⏱ 复杂度分析

指标 复杂度
时间复杂度 O(n log n)
空间复杂度 O(1)(无递归栈)

🔍 解法一 vs 解法二 对比总结

维度 解法一:自顶向下递归 解法二:自底向上迭代
思想 递归拆链 固定步长合并
是否递归 ✅ 是 ❌ 否
空间复杂度 O(log n) O(1)
实现难度 ⭐⭐⭐(易理解) ⭐⭐⭐⭐(细节多)
切链方式 快慢指针 按长度切割
合并方式 返回头结点 返回头 + 尾
面试推荐 ✅ 高频 ✅ 进阶 / 面试官追问

✅ 总结

  • 解法一适合:快速写出、逻辑清晰、面试首选
  • 解法二 适合:
    • 要求 O(1) 空间
    • 考察对链表操作的熟练度
    • 进阶 / 高级面试

一句话总结:

递归写法优雅,迭代写法极致。

相关推荐
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开发中错误处理策略:网络异常统一处理
算法
小小杨树4 天前
读懂色彩:拍照调色不再难
算法·计算机视觉·配色