题目描述
给定一个单链表,请使用 $O(nlogn)$ 的时间复杂度和额外 $O(1)$ 的空间复杂度对其进行排序。
样例1
输入:4->2->1->3
输出:1->2->3->4
样例2
输入:-1->5->3->4->0
输出:-1->0->3->4->5
算法
(归并排序) $时间:O(nlogn), 空间O(1)$
自顶向下递归形式的归并排序,由于递归需要使用系统栈,递归的最大深度是 $logn$,所以需要额外 $O(logn)$ 的空间。
所以我们需要使用自底向上非递归形式的归并排序算法。
基本思路是这样的,总共迭代 $logn$ 次:
- 第一次,将整个区间分成连续的若干段,每段长度是2:$[a_0, a_1], [a_2, a_3], … [a_{n-1}, a_{n-1}]$, 然后将每一段内排好序,小数在前,大数在后;
- 第二次,将整个区间分成连续的若干段,每段长度是4:$[a_0, …, a_3], [a_4,…, a_7], … [a_{n-4}, …, a_{n-1}]$,然后将每一段内排好序,这次排序可以利用之前的结果,相当于将左右两个有序的半区间合并,可以通过一次线性扫描来完成;
- 依此类推,直到每段小区间的长度大于等于 $n$ 为止;
另外,当 $n$ 不是2的整次幂时,每次迭代只有最后一个区间会比较特殊,长度会小一些,遍历到指针为空时需要提前结束。
时间复杂度分析:整个链表总共遍历 $logn$ 次,每次遍历的复杂度是 $O(n)$,所以总时间复杂度是 $O(nlogn)$。
空间复杂度分析:整个算法没有递归,迭代时只会使用常数个额外变量,所以额外空间复杂度是 O(1)$.
C++ 代码
/**
* 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;
}
};
解释了一下Y总的代码,方便大家阅读
对于第二段不满的情况怎么排序的?
就是正常排啊。。通过&&q在走到末尾的时候就终止了,而这时cur已经走过了第二段所有的点,故此时第二段的点已经完成归并排序了,接下来就是对第一段剩下的点进行归并。
感谢
只有最后一段长度可能小于 $i$,前面都等于 $i$。分析见
https://www.acwing.com/activity/content/code/content/6893820/
另外 dummy 在栈上分配就不用 delete 了,也快一点。
麻了,同样的代码用java就过不了
为啥这种写法。不要考虑手动给链表最后一个节点置为null
太难了
这题处理边界简直神烦T T
是的hh
话说这道题能不能用递归写呀?非指针版我一直用的递归。
其他的题解有递归
谢谢
不能用递归。因为题目要求只能使用额外 $O(1)$ 的空间,如果使用递归,那么就需要额外 $O(logn)$ 的空间了。
谢谢老师
for (int j = 0; j + i < n; j += i * 2)请问y神,这个j + i有啥含义么,没有看懂诶
第二段的开头是
j + i
,如果这个值越界,说明只有一段,不需要归并了。没注释看懵逼了
这题直接看代码太抽象了,建议看一下文字讲解。
有没有快排的版本啊,这道题面试官也很可能强行要求用快排的
快排的额外空间复杂度是 $O(logn)$,不满足题目要求哇。
等号是从右到左幅值吗?
对滴
能解释下f,s,j这三个变量的用途吗
f
:二路归并第一段的指针;s
:二路归并第二段的指针;j
:每段二路归并的起点