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

一句话总结:

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

相关推荐
凤凰院凶涛QAQ1 小时前
《Java版数据结构 & 集合类剖析》栈与队列:“push/pop 是栈的灵魂,offer/poll 是队列的骨架——四组 API,两种人生”
java·开发语言·数据结构
科技大视界1 小时前
投资AI项目,传统尽调不够用了——李章虎律师拆解算法、数据、算力三大雷区
人工智能·算法·数据挖掘
郝学胜-神的一滴1 小时前
算法实战:最小k个数——大顶堆的优雅解法
开发语言·数据结构·c++·python·程序人生·算法·排序算法
Irissgwe1 小时前
算法滑动窗口
数据结构·算法
怪兽学LLM2 小时前
LeetCode 105. 从前序与中序遍历序列构造二叉树:分治递归思路详解
算法·leetcode·职场和发展
可编程芯片开发2 小时前
基于PI控制的三相整流器控制系统的simulink建模与仿真,包含超级电容充电和电机
算法
酿情师2 小时前
区块链共识算法深度拆解:PoW、PoS、PBFT、Raft 原理解析
算法·区块链·共识算法
hansang_IR2 小时前
【记录】「SCOI2016」三道模拟赛/26.7.12
c++·算法
退休倒计时3 小时前
【每日一题】LeetCode 39. 组合总和 TypeScript
算法·leetcode·typescript
不是az3 小时前
力控相关知识点
算法·机器学习·最小二乘法