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

一句话总结:

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

相关推荐
凯瑟琳.奥古斯特1 小时前
力扣1235完整解法详解
java·开发语言·leetcode
嘿黑嘿呦1 小时前
数据结构-图论-最小生成树
数据结构·算法·图论
Justice Young1 小时前
算法分析与设计实验:贪心法求解0/1背包问题的局限性
算法
黎阳之光1 小时前
无感定位·智管全域:黎阳之光人员无感定位管理系统,重新定义安全与效率
人工智能·物联网·算法·安全·数字孪生
小许同学记录成长1 小时前
网格简化算法 — Edge Collapse(边塌缩)
qt·算法
凯瑟琳.奥古斯特2 小时前
力扣1001网格照明解法
算法·leetcode·职场和发展
fengenrong2 小时前
20260601
算法·深度优先·图论
晚笙coding2 小时前
从“看起来像双指针”到真正的动态规划 —— 最长公共子序列
算法·动态规划
05候补工程师2 小时前
【考研高数核心突破】极限的本质、高频解题套路与海涅定理深度解析(附经典例题思维导图式拆解)
经验分享·笔记·考研·算法