148. Sort List
Sort a linked list in O(n log n) time using constant space complexity.
能想到的时间复杂度为 O(n log n) 的排序算法便只有快速排序, 堆排序,以及归并排序。
在这里给出六大基本排序算法的思想,由于个人表达能力有限,所以如有不清楚的可以先去看看,我就不再赘述这几个排序的实现啦。
而堆排序需要维护一个最小化堆,这个堆如果用数组(初始坐标为0)的形式维护,若父亲的下标为n,那么左儿子的下标为2n+1, 右儿子的下标为2n+2。对于数组形式,各元素可以随机访问,那么对于堆的维护需要的时间复杂度为O(log n)。但如果是链表形式,需要顺序访问之前的每个元素才能找到需要的元素,堆维护所需的时间复杂度退化为O(n),整体算法的空间复杂度退化为O(n2)。于是,堆排序不符合题意。
还剩的两个备用选项分别是快速排序,与分治排序。这两种排序的实现均为分治策略,所以需要在这里需要再简单扯一下分治算法时间复杂度的计算方法好了,扯得不好请多批评,万分感谢!
对于分治算法来说,有一个很常见但是却不怎么容易懂的关系式,叫做T(n) <= 2T(n/2) + cn 并且 T(2) <= c。
这个关系式的中文解释叫做:把输入分成相等大小的两半; 通过递归在这两部分分别求解两个子问题,然后把这两个结果组合成一个全局的解,对于初始的划分与最后的重新组合仅使用线性时间。
如上图所示,对于每个分治之后的子问题,当初始的划分与最后的重新组合仅使用线性时间时:
一)分析最初的几级:
第一层:有一个规模为n的问题,它所用的时间最多为cn(cn)加上在所有后来的递归调用中用的时间( 2T(n/2))。// T(n) <= 2T(n/2) + cn
第二层:有两个规模各自为n/2的问题。其中任意一个用时最多为cn/2,再加上后面的递归调用所用时间(理由同第一层)。所以该层所用的时间最多为cn。
第三层:有规模为n/4的四个问题,每个分支用cn/4+剩余调用的时间。所用该层总用时依然为cn。
二)识别一个模式:
在递归的第j层,子问题的个数双倍了j次,因此现在总问题的个数有2j个。
而每个问题的规模对应收缩,所以每个问题的规模是n/2j,因此每个用时间最多cn/2j+剩余调用。
于是,该层的所有子文题用时为2j * cn/2j = cn。
三)在递归的所有层上求和:
由上述的识别可得,对于可以以线性时间完成初始的划分与最后的重新组合时,O(cn)适用于每一层执行的工作总量。而为了使问题的规模从n减小到2 (T(2) <= c),输入必须被减半的次数是log2 n。
因此在log2 n层上对cn的工作求和,我们可以得到O(cn log n)的运行时间。
那么现在来看快速排序,快速排序的每个分支中需要对整个空间进行划分,假设是选定表头元素执行划分,顺序选定的元素使用的空间复杂度为O(1),与使用数组时相同。但逆向选定元素时,却需要顺序遍历之前所有元素,来找到当前空间中最后一个比选定元素小的元素,所需时间复杂度为O(n)。因此,每个分支中完成划分所需的时间复杂度为O(n2)。
所以在这里再扯一下,不能线性完成划分与组合时,所需的时间复杂度。太懒了放张图片上来好了Orz...
其实相当于从第一层,一直加到第log2 n层,是一个等比数列求和公式,最后得到的结果是Cn2。
由以上推导可看出,符合时间复杂度的就只有归并排序了,因为归并排序不在循环中要求随机访问元素。但对于常数级的空间复杂度(constant space complexity),我在这里是持悲观态度的,因为归并排序需要使用递归,而递归必然会占用堆栈的空间,而且这个占用是与层数相关的。这里的层数是log2n,而不是一个常量,所以我也不太明白为什么能AC,那么这里的空间复杂度的要求就随便看看好了Orz...
以下是AC的代码:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
public class Solution {
public ListNode sortList(ListNode head) {
if (null == head || null == head.next){
return head;
}
ListNode middle = divide(head);
head = sortList(head);
middle = sortList(middle);
if (null == head){
return middle;
} else if (null == middle){
return head;
}
ListNode newHead = null;
if (head.val < middle.val){
newHead = head;
head = head.next;
} else {
newHead = middle;
middle = middle.next;
}
ListNode ans = newHead;
while (head != null && middle != null){
if (head.val < middle.val){
newHead.next = head;
head = head.next;
} else {
newHead.next = middle;
middle = middle.next;
}
newHead = newHead.next;
}
newHead.next = null == head ? middle : head;
return ans;
}
private ListNode divide(ListNode head){
if (null == head || null == head.next){
return head;
}
ListNode fast = head.next, slow = head;
while (fast != null){
fast = fast.next;
if (fast != null){
fast = fast.next;
} else {
break;
}
slow = slow.next;
}
ListNode middle = slow.next;
slow.next = null;
return middle;
}
}