Sort a linked list in O(n log n) time using constant space complexity.
思路分析:这题要求在O(n log n) 和常量空间对单链表排序,O(n log n) 的排序算法有快速排序,归并排序和堆排序,对于快速排序,其最坏情况下的时间复杂读是O(n),所以不符合要求。我们可以用归并排序解决这题。用归并排序的好处是平均时间和最坏时间都是O(n log n) ,并且我们不需要额外空间就可以merge两个有序的链表使得merge之后仍然有序。这里插一句,链表和数组的区别在于链表不支持元素按照index随机访问,因此实现依赖于index访问元素的算法会比较麻烦,但是链表也有其自身的优势,就是在任意位置插入或者删除元素都是常量时间,因为不需要移动大量元素“腾地方”。在数组上实现归并排序和在链表上实现归并排序基本算法一致,都是先不断对半划分数组,直到每个元组只剩下一个元素的时候开始merge元素,递归划分,划分完毕进行merge。下图给出的算法过程的形象表示(来自这里):
划分的过程数组和链表是类似的,只是我们需要找到链表的中点,这个可以用快慢指针法解决。划分完毕后merge的过程二者有不同,对于数组我们会使用额外tmp数组保存merge之后的数组结果,merge结束后拷贝回原来数组。对于链表,我们不需要额外空间,那么如何merge两个有序链表使得merge之后仍然有序呢?这就是LeetCode Merge Two Sorted List 问题,我以merge1 3 5 和 2 4 6的过程为例,单步图解如下
理解了快慢指针找链表中点,merge两个sorted list的过程和merge sort的划分合并过程后,解决这题就很容易了。
AC Code
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode sortList(ListNode head) {
return mergeSortList(head);
}
public ListNode mergeSortList(ListNode head) {//split the list into two partitions
if(head == null || head.next == null) return head;
ListNode fast = head;
ListNode slow = head;
while(fast.next != null && fast.next.next != null){//fast must arrive the end firstly
fast = fast.next;
fast = fast.next;
slow = slow.next;
}
ListNode head1 = head;
ListNode head2 = slow.next;
slow.next = null;//split the list from the middle node
head1 = mergeSortList(head1);
head2 = mergeSortList(head2);
return merge(head1, head2);
}
public ListNode merge(ListNode head1, ListNode head2){
ListNode fakeHead = new ListNode(0);
fakeHead.next = head1;
ListNode pre = fakeHead;
while(head1 != null && head2 != null){
if(head1.val < head2.val){
head1 = head1.next;
} else {
ListNode next = head2.next;
head2.next = pre.next;
pre.next = head2;
head2 = next;
}
pre = pre.next;
}
if(head2 != null){
pre.next = head2;
}
return fakeHead.next;
}
}
解题过程中参考了 http://blog.csdn.net/linhuanmars/article/details/21133949 但是这个解法由于是递归实现,仍然需要(O(logn))的栈空间。迭代实现的解法可以参考 这里。