LeetCode 148. Sort List

106 阅读1分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第16天,点击查看活动详情

LeetCode 148. Sort List

给你链表的头结点 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 * 104] 内
  • -105 <= Node.val <= 105

 

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

算法

(归并排序) 时间:O(nlogn),空间O(1) 自顶向下递归形式的归并排序,由于递归需要使用系统栈,递归的最大深度是 logn,所以需要额外 O(logn) 的空间。 所以我们需要使用自底向上非递归形式的归并排序算法。 基本思路是这样的,总共迭代 logn 次:

第一次,将整个区间分成连续的若干段,每段长度是2:[a0,a1],[a2,a3],…[an−1,an−1], 然后将每一段内排好序,小数在前,大数在后; 第二次,将整个区间分成连续的若干段,每段长度是4:[a0,…,a3],[a4,…,a7],…[an−4,…,an−1],然后将每一段内排好序,这次排序可以利用之前的结果,相当于将左右两个有序的半区间合并,可以通过一次线性扫描来完成; 依此类推,直到每段小区间的长度大于等于 n 为止; 另外,当 n 不是2的整次幂时,每次迭代只有最后一个区间会比较特殊,长度会小一些,遍历到指针为空时需要提前结束。

时间复杂度分析:整个链表总共遍历 logn 次,每次遍历的复杂度是 O(n),所以总时间复杂度是 O(nlogn)。 空间复杂度分析:整个算法没有递归,迭代时只会使用常数个额外变量,所以额外空间复杂度是 O(1).

ac 代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* sortList(ListNode* head) {
        int n = 0;
        for (auto p = head; p; p = p->next) n ++ ;

        auto dummy = new ListNode(-1);
        dummy->next = head;
        for (int i = 1; i < n; i *= 2) {
            auto cur = dummy;
            for (int j = 1; j + i <= n; j += i * 2) {
                auto p = cur->next, q = p;
                for (int k = 0; k < i; k ++ ) q = q->next;
                int x = 0, y = 0;
                while (x < i && y < i && p && q) {
                    if (p->val <= q->val) cur = cur->next = p, p = p->next, x ++ ;
                    else cur = cur->next = q, q = q->next, y ++ ;
                }
                while (x < i && p) cur = cur->next = p, p = p->next, x ++ ;
                while (y < i && q) cur = cur->next = q, q = q->next, y ++ ;
                cur->next = q;
            }
        }

        return dummy->next;
    }
};