开启掘金成长之旅!这是我参与「掘金日新计划 · 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;
}
};