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) 空间
    • 考察对链表操作的熟练度
    • 进阶 / 高级面试

一句话总结:

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

相关推荐
码流子12 分钟前
2026 图像数据标注工具横评:LabelImg / CVAT / Label Studio / X-AnyLabeling / 国产Web平台,到底怎么选?
大数据·人工智能·算法
hansang_IR12 分钟前
【题解】[JSOI2016] 最佳团体(分数规划)
c++·算法
羊羊小栈27 分钟前
基于「YOLO目标检测 + 多模态AI分析」的公共场所暴力安全智能检测分析预警系统
人工智能·算法·面试·毕业设计·大作业
土星云SaturnCloud37 分钟前
ResNet-50 图像分类算法在边缘微服务器上的部署与性能评测
服务器·人工智能·算法·边缘计算·resnet-50
Niuguangshuo39 分钟前
文本-音频时间戳对齐:5 个开源工具实测
算法·开源·音视频
Niuguangshuo44 分钟前
论文解读:RNN-Transducer,端到端流式 ASR 的骨架
算法·语音识别
GreenTea1 小时前
🔥别再用压缩了,Codex、NVIDIA、DeepSeek、Uber 给出 Agent 上下文管理的新答案
前端·后端·算法
All for pursuit.1 小时前
【矩阵-2】240.搜索二维矩阵 II
数据结构·c++·算法·leetcode
6Hzlia1 小时前
【Classic 150 刷题计划】 LeetCode 209. 长度最小的子数组 | C++ 滑动窗口(毛毛虫算法)经典模板
c++·算法·leetcode
6Hzlia2 小时前
【Classic 150 刷题计划】 LeetCode 28. 找出字符串中第一个匹配项的下标 | C++ 滑动窗口与子串比对
c++·算法·leetcode