排序链表

**给你链表的头节点 head

请将其按升序排列,并返回排序后的链表。

示例 1:

输入:head = [4,2,1,3]
输出:[1,2,3,4]

示例 2:

输入:head = [-1,5,3,4,0]
输出:[-1,0,3,4,5]

示例 3:

输入:head = []
输出:[]

提示:

  • 链表中节点的数目在范围 [0, 5 * 10^4]
  • -10^5 <= Node.val <= 10^5

进阶:

你可以在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序吗?

解法一(递归归并排序):

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* sortList(ListNode* head) {
        if (head == nullptr || head->next == nullptr)
        {
            return head;
        }

        ListNode* slow = head;
        ListNode* fast = head->next;

        while (fast != nullptr && fast->next != nullptr)
        {
            slow = slow->next;
            fast = fast->next->next;
        }

        ListNode* rightHead = slow->next;
        slow->next = nullptr;

        ListNode* left = sortList(head);
        ListNode* right = sortList(rightHead);

        return merge(left, right);
    }

private:
    ListNode* merge(ListNode* list1, ListNode* list2) {
        ListNode dummy(0);
        ListNode* tail = &dummy;

        while (list1 != nullptr && list2 != nullptr)
        {
            if (list1->val <= list2->val)
            {
                tail->next = list1;
                list1 = list1->next;
            }
            else
            {
                tail->next = list2;
                list2 = list2->next;
            }

            tail = tail->next;
        }

        tail->next = list1 != nullptr ? list1 : list2;

        return dummy.next;
    }
};

解法二(自底向上归并排序):

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* sortList(ListNode* head) {
        if (head == nullptr || head->next == nullptr)
        {
            return head;
        }

        int length = 0;
        ListNode* cur = head;

        while (cur != nullptr)
        {
            length++;
            cur = cur->next;
        }

        ListNode dummy(0, head);

        for (int subLen = 1; subLen < length; subLen <<= 1)
        {
            ListNode* prev = &dummy;
            cur = dummy.next;

            while (cur != nullptr)
            {
                ListNode* head1 = cur;
                ListNode* head2 = split(head1, subLen);
                cur = split(head2, subLen);

                prev->next = merge(head1, head2);

                while (prev->next != nullptr)
                {
                    prev = prev->next;
                }
            }
        }

        return dummy.next;
    }

private:
    ListNode* split(ListNode* head, int size) {
        if (head == nullptr)
        {
            return nullptr;
        }

        for (int i = 1; i < size && head->next != nullptr; i++)
        {
            head = head->next;
        }

        ListNode* second = head->next;
        head->next = nullptr;

        return second;
    }

    ListNode* merge(ListNode* list1, ListNode* list2) {
        ListNode dummy(0);
        ListNode* tail = &dummy;

        while (list1 != nullptr && list2 != nullptr)
        {
            if (list1->val <= list2->val)
            {
                tail->next = list1;
                list1 = list1->next;
            }
            else
            {
                tail->next = list2;
                list2 = list2->next;
            }

            tail = tail->next;
        }

        tail->next = list1 != nullptr ? list1 : list2;

        return dummy.next;
    }
};

核心思想

链表排序最适合使用归并排序。

原因是链表不像数组那样可以随机访问,快速排序或堆排序在链表上并不方便。

归并排序只需要做两件事:

  • 把链表拆成两段
  • 把两个已经有序的链表合并成一个有序链表

合并两个有序链表对链表来说非常自然,只需要调整 next 指针即可。

这题最关键的观察是:

链表不能高效随机访问,但可以很方便地断开、拼接和合并,所以归并排序是链表排序的常用做法。

递归归并排序写起来更直观,但递归调用栈需要 O(log n) 空间。

进阶要求常数级空间,所以更推荐自底向上的迭代归并排序。

解法一:递归归并排序

递归归并排序分三步。

1. 找到链表中点

使用快慢指针:

  • slow 每次走一步
  • fast 每次走两步

fast 到达链表末尾时,slow 大致位于链表中间。

代码中初始化为:

ListNode* slow = head;
ListNode* fast = head->next;

这样在偶数长度链表中,slow 会停在左半段的最后一个节点,方便断开链表。

2. 断开左右两段

找到中点后:

ListNode* rightHead = slow->next;
slow->next = nullptr;

左半段从 head 开始。

右半段从 rightHead 开始。

断开以后,两段链表可以分别递归排序。

3. 合并两个有序链表

递归排序左右两段:

ListNode* left = sortList(head);
ListNode* right = sortList(rightHead);

然后把两个有序链表合并:

return merge(left, right);

合并过程和“合并两个有序链表”一样,每次取两个链表当前头节点中较小的那个接到结果后面。

解法二:自底向上归并排序

递归归并排序的问题是递归栈不满足常数空间要求。

自底向上归并排序不使用递归,而是从长度为 1 的小段开始合并。

具体过程是:

长度 1 的有序段两两合并 -> 长度 2
长度 2 的有序段两两合并 -> 长度 4
长度 4 的有序段两两合并 -> 长度 8
...

每一轮的子链表长度是 subLen

每轮结束后:

subLen <<= 1

直到 subLen >= length,整个链表就已经有序。

split 函数做什么

split(head, size) 的作用是:

  • head 开始截出长度最多为 size 的一段链表
  • 把这段链表和后面的部分断开
  • 返回下一段链表的头节点

例如:

1 -> 4 -> 2 -> 3

size = 2 时,从 1 开始截出:

1 -> 4

返回下一段的头节点:

2

同时把第一段末尾的 next 置为 nullptr

这样后续 merge 时,每一段都是独立链表,不会串到后面的未处理节点。

为什么自底向上是常数空间

递归归并排序需要递归调用栈。

自底向上归并排序用循环控制段长度和当前位置,只使用:

  • dummy
  • prev
  • cur
  • head1
  • head2

这些指针变量。

没有使用额外数组,也没有递归调用栈。

所以如果不把原链表节点本身计入额外空间,它的空间复杂度是 O(1)

边界情况

如果链表为空:

head = []

直接返回 nullptr

如果链表只有一个节点,已经有序,直接返回 head

如果链表中有重复值,例如:

head = [1,1,1]

归并时使用:

list1->val <= list2->val

可以正常处理相等节点,结果仍然是升序链表。

如果链表长度不是 2 的整数次幂,自底向上归并中最后一段长度可能不足 subLen

split 会自然截出剩余部分,不需要额外特殊处理。

正确性证明

我们证明:归并排序返回的链表按升序排列,并且包含原链表中的所有节点。

结论 1:merge 能把两个有序链表合并成一个有序链表

合并时,每次比较两个链表当前头节点。

较小的节点一定是两个链表所有未处理节点中的最小值。

把它接到结果链表末尾,不会破坏升序。

重复这个过程,直到某个链表为空。

剩余链表本身已经有序,可以直接接到结果末尾。

因此 merge 返回的链表是升序的,并且包含两个输入链表的全部节点。

结论 2:递归归并排序能正确排序任意长度链表

当链表为空或只有一个节点时,它本身已经有序,直接返回正确。

当链表长度大于 1 时,算法把链表拆成左右两段。

根据递归假设,左右两段经过 sortList 后分别有序。

再根据结论 1,把两个有序链表合并后,得到的整条链表也是有序的。

因此递归归并排序正确。

结论 3:自底向上归并每一轮后,长度为 subLen 的连续段都是有序的

初始时,subLen = 1

每个单独节点都可以看成一个有序段。

一轮合并中,算法每次取出两个长度最多为 subLen 的有序段,并用 merge 合并成一个长度最多为 2 * subLen 的有序段。

根据结论 1,合并后的段仍然有序。

所以这一轮结束后,长度为 2 * subLen 的连续段都是有序的。

结论 4:自底向上归并结束时,整个链表有序

每一轮都会让有序段长度翻倍。

subLen >= length 时,整个链表已经被视为一个有序段。

因此最终返回的链表整体升序排列。

得出结论

由结论 1 可知,合并两个有序链表的过程正确。

由结论 2 可知,递归归并排序正确。

由结论 3 和结论 4 可知,自底向上归并排序正确。

因此两个解法都能返回排序后的链表。

举例理解

以:

head = [4,2,1,3]

为例。

递归归并排序会先拆成:

[4,2] 和 [1,3]

继续拆:

[4] [2] [1] [3]

然后两两合并:

[2,4] 和 [1,3]

最后合并:

[1,2,3,4]

所以返回:

[1,2,3,4]

复杂度分析

解法一

每一层递归都会遍历链表进行拆分和合并。

递归层数是 O(log n)

  • 时间复杂度:O(n log n)
  • 空间复杂度:O(log n)

其中空间复杂度来自递归调用栈。

解法二

自底向上归并排序每一轮都会遍历链表一次。

一共需要 O(log n) 轮。

  • 时间复杂度:O(n log n)
  • 空间复杂度:O(1)

解法二满足进阶要求,是更推荐的做法。**