Sort a linked list in O(n log n) time using constant space complexity.
这道题目非常简短的一句话,给链表排序,看到nlogn,我们可以来简单复习一下排序。首先说一下这个nlogn的时间复杂度(根据决策树我们可以得出这个界限),是基于比较排序的最小上限,也就是说,对于没有一定范围情况的数据来说,最快的排序思路就是归并和快速排序了(当然具体的参数系数还是由更具体的设置决定的)。对于数组的话,如果使用归并排序,不是in place的,因为我们需要额外申请一个(N)空间用于merge的时候使用,所以导致了数据的复制和传递,因此不适合作为主存排序。(内存的数据变化量可是惊人的呀)。但是,对于链表的归并排序,就可以做到in place,原因在于,链表本来就是一个动态的数据结构,我们在merge的时候改变一下指针的指向就OK了。之前,我在帮公司写一个项目的时候,support组提供的数据结构接口不是很满意,我就自己封装了一个利用归并来排序的链表,感觉用起来还行(强迫症?)。顺便提一下,最基本的排序,插入排序,n^2的时间复杂度;以及历史上第一次突破平方界限的排序,希尔排序,其实就是插入排序的改良版,时间复杂度取决于因子的选取(具体我忘记了),废话太多了>.< orz。
下面是代码,其中一些个人觉得重要的地方也做了注释。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *findMedian(ListNode *head){
ListNode *slow = head;
ListNode *fast = head;
while(fast->next != NULL && fast->next->next != NULL){ //这里注意先后顺序,必须先保证slow->next = NULL
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
ListNode *merge(ListNode *a, ListNode *b){
ListNode *dummyNode = new ListNode(0); //在头节点不确定或者需要删除时,引入哑节点是很好的选择
ListNode *pos = dummyNode;
while(a != NULL && b != NULL){
if (a->val <= b->val){
pos->next = a;
a = a->next;
}
else{
pos->next = b;
b = b->next;
}
pos = pos->next;
}
pos->next = a != NULL ? a : b; //这里用问号表达式可以减少代码量
return dummyNode->next;
}
ListNode *sortList(ListNode *head) {
if (head == NULL || head->next == NULL)
return head;
ListNode *mid = findMedian(head);
ListNode *next = mid->next;
mid->next = NULL; //这里需要注意,一定要断开链表,所以重新申请了节点指针next而不是直接将mid = mid->next
return merge(sortList(head), sortList(next));
}
};