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

一句话总结:

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

相关推荐
O。O蛋黄酥啊几秒前
GraphRAG 和 LightRAG 详解与对比
人工智能·python·算法·rag·graphrag·lightrag
Σίσυφος19004 分钟前
depth_from_focus 详解
算法
圣保罗的大教堂7 分钟前
leetcode 2996. 大于等于顺序前缀和的最小缺失整数 简单
leetcode
疯狂打码的少年15 分钟前
【数据结构】八大排序算法对比总结(时间/空间/稳定性)
数据结构·笔记·算法
树獭哥25 分钟前
量化金融入门:从数据规则到随机游走与凯利公式
人工智能·算法·金融·量化金融
圣保罗的大教堂30 分钟前
leetcode 3702. 按位异或非零的最长子序列 中等
leetcode
薛定e的猫咪1 小时前
(arXiv 2026)GLiBRL :可学习基函数的深度贝叶斯元强化学习 ----待补充
人工智能·深度学习·学习·算法·机器学习
hetao17338371 小时前
2026-08-21~23 hetao1733837 的刷题记录
c++·算法
青 春 记 忆2 小时前
LeetCode 206. 反转链表|Python 解法详解
python·leetcode·链表