LeetCode LCR 077. 排序链表

题目描述

LCR 077. 排序链表

思路分析

解法一:自顶向下归并排序(推荐)

核心思路

  • 链表不支持随机访问,无法直接使用快速排序,但归并排序天然适合链表——合并两个有序链表是 O(n) 的经典操作。
  • 第一步:找中点(快慢指针)slow 每次走 1 步,fast 每次走 2 步,fast 走到末尾时 slow 恰好位于中点。令 fast = head.next 出发(而非 head),使 slow 停在左半部分的最后一个节点,便于断链。
  • 第二步:断链递归:记录 mid = slow.next,再令 slow.next = nil 断开左右两段,分别递归排序 sortList(head)sortList(mid)
  • 第三步:合并有序链表:使用虚拟头节点逐节点比较,将较小节点依次接入结果链表,即 LeetCode 21 题的经典做法。

复杂度分析

  • 时间复杂度:O(n log n),其中 n 表示链表节点数,共 log n 层递归,每层合并操作为 O(n)
  • 空间复杂度:O(log n),其中 log n 表示递归调用栈深度
class Solution {

    public ListNode sortList(ListNode head) {
        // 递归终止条件:空链表或单节点链表已有序
        if (head == null || head.next == null) {
            return head;
        }
        // 快慢指针找中点:fast 从 head.next 出发,slow 最终停在左半末尾
        ListNode slow = head, fast = head.next;
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }
        // 记录右半起始节点,断开左右两段
        ListNode mid = slow.next;
        slow.next = null;
        // 递归排序左右两段,再合并
        return merge(sortList(head), sortList(mid));
    }

    private ListNode merge(ListNode l1, ListNode l2) {
        // 虚拟头节点简化边界处理
        ListNode dummy = new ListNode(0);
        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;
        return dummy.next;
    }
}
func sortList(head *ListNode) *ListNode {
    // 递归终止条件:空链表或单节点链表已有序
    if head == nil || head.Next == nil {
        return head
    }

    // 快慢指针找中点:fast 从 head.Next 出发,slow 最终停在左半末尾
    slow, fast := head, head.Next
    for fast != nil && fast.Next != nil {
        slow = slow.Next
        fast = fast.Next.Next
    }

    // 记录右半起始节点,断开左右两段
    mid := slow.Next
    slow.Next = nil

    // 递归排序左右两段,再合并
    left := sortList(head)
    right := sortList(mid)
    return mergeSorted(left, right)
}

// mergeSorted 合并两个有序链表
func mergeSorted(l1, l2 *ListNode) *ListNode {
    // 虚拟头节点简化边界处理
    dummy := &ListNode{}
    cur := dummy

    for l1 != nil && l2 != nil {
        // 取较小节点接入结果链表
        if l1.Val < l2.Val {
            cur.Next = l1
            l1 = l1.Next
        } else {
            cur.Next = l2
            l2 = l2.Next
        }
        cur = cur.Next
    }

    // 将剩余节点直接接上
    if l1 != nil {
        cur.Next = l1
    }
    if l2 != nil {
        cur.Next = l2
    }

    return dummy.Next
}

解法二:自底向上归并排序(O(1) 空间)

核心思路

  • 解法一递归调用栈占用 O(log n) 空间,若追求严格 O(1) 空间,需改用迭代的自底向上归并。
  • 初始将每个节点视为长度为 1 的有序子链表,按步长 subLen = 1, 2, 4, 8, ... 依次两两合并,直到步长超过链表长度。
  • 每轮遍历链表,按当前步长切出左右两段子链表进行合并,将合并结果拼接到已处理部分的末尾。
  • 使用虚拟头节点 dummy 统一处理拼接逻辑,避免频繁修改头指针。

复杂度分析

  • 时间复杂度:O(n log n),其中 n 表示链表节点数,共 log n 轮,每轮合并操作为 O(n)
  • 空间复杂度:O(1),仅使用常数个辅助指针,无递归栈开销
class Solution {

    public ListNode sortList(ListNode head) {
        // 计算链表长度
        int length = 0;
        ListNode node = head;
        while (node != null) {
            length++;
            node = node.next;
        }

        ListNode dummy = new ListNode(0, head);
        // 步长从 1 开始,每次翻倍,直到超过链表长度
        for (int subLen = 1; subLen < length; subLen <<= 1) {
            ListNode pre = dummy, cur = dummy.next;
            while (cur != null) {
                // 切出左半段(长度为 subLen)
                ListNode head1 = cur;
                for (int i = 1; i < subLen && cur.next != null; i++) {
                    cur = cur.next;
                }
                // 切出右半段(长度至多为 subLen)
                ListNode head2 = cur.next;
                cur.next = null;
                cur = head2;
                for (int i = 1; i < subLen && cur != null && cur.next != null; i++) {
                    cur = cur.next;
                }
                // 保存下一段起始位置,断开右半段
                ListNode next = null;
                if (cur != null) {
                    next = cur.next;
                    cur.next = null;
                }
                // 合并左右两段,拼接到已处理链表末尾
                ListNode merged = merge(head1, head2);
                pre.next = merged;
                while (pre.next != null) {
                    pre = pre.next;
                }
                cur = next;
            }
        }
        return dummy.next;
    }

    private ListNode merge(ListNode l1, ListNode l2) {
        ListNode dummy = new ListNode(0);
        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;
        return dummy.next;
    }
}
func sortList(head *ListNode) *ListNode {
    // 计算链表长度
    length := 0
    node := head
    for node != nil {
        length++
        node = node.Next
    }

    dummy := &ListNode{Next: head}
    // 步长从 1 开始,每次翻倍,直到超过链表长度
    for subLen := 1; subLen < length; subLen <<= 1 {
        pre, cur := dummy, dummy.Next
        for cur != nil {
            // 切出左半段(长度为 subLen)
            head1 := cur
            for i := 1; i < subLen && cur.Next != nil; i++ {
                cur = cur.Next
            }
            // 切出右半段(长度至多为 subLen)
            head2 := cur.Next
            cur.Next = nil
            cur = head2
            for i := 1; i < subLen && cur != nil && cur.Next != nil; i++ {
                cur = cur.Next
            }
            // 保存下一段起始位置,断开右半段
            var next *ListNode
            if cur != nil {
                next = cur.Next
                cur.Next = nil
            }
            // 合并左右两段,拼接到已处理链表末尾
            merged := mergeSorted(head1, head2)
            pre.Next = merged
            for pre.Next != nil {
                pre = pre.Next
            }
            cur = next
        }
    }
    return dummy.Next
}

// mergeSorted 合并两个有序链表
func mergeSorted(l1, l2 *ListNode) *ListNode {
    dummy := &ListNode{}
    cur := dummy
    for l1 != nil && l2 != nil {
        if l1.Val <= l2.Val {
            cur.Next = l1
            l1 = l1.Next
        } else {
            cur.Next = l2
            l2 = l2.Next
        }
        cur = cur.Next
    }
    if l1 != nil {
        cur.Next = l1
    }
    if l2 != nil {
        cur.Next = l2
    }
    return dummy.Next
}

相似题目

题目 难度 考察点
21. 合并两个有序链表 简单 归并的核心子步骤
23. 合并 K 个升序链表 困难 归并 + 优先队列
912. 排序数组 中等 数组版归并排序
147. 对链表进行插入排序 中等 链表排序变体
1669. 合并两个链表 中等 链表合并操作
剑指 Offer II 078. 合并排序链表 困难 多链表归并
本文作者:
本文链接: https://hgnulb.github.io/blog/2026/22754907
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!