https://oj.leetcode.com/problems/sort-list/
Sort a linked list in O(n log n) time using constant space complexity.
public ListNode sortList(ListNode head)
这一题给出的提示就是算法复杂度,nlogn。很长一段时间我认为nlogn去排序一个链表是不可能的,在某次面试imdb的时候面试官问我是否可以,我表示不可以,面试官表示真的不可以?我表示真的不行。面试官就不高兴了。。。
事实上如果是一个数组,nlogn的排序算法有两个,一个是归并排序,另一个是快排。归并排序是一个稳定的nlogn排序算法,但同时也需要额外的nlogn的空间去完成,因为归并排序,将两个排好序的子数组合并的这一步,是无论如何都没办法Inplace完成的。快排则是一个不稳定的nlogn算法,复杂度跟元素分布高度相关,胜在可以inplace,同时也有好几种优化的方式,最常见的就是在partitioning的过程里,pivot的选取从固定某个位置变成随机选取,这样做的目的在于将复杂度和元素分布的关联度切断。
那么这一题到底是快排还是归并排序,其实仔细分析下会发现LinkedList里,归并排序更好发挥,而快排由于pivot没法随机选取,限制较大,而且这是一个单向链表,也就是把pivot确定之后,以pivot为界分开两边这一步骤也会稍嫌麻烦。相反LinkedList给归并排序带来了inplace的可能。因为原本无法inplace的合并这一步,变成了可能。那么,下面就给出归并排序的代码:
public ListNode sortList(ListNode head) {
if(head == null)
return null;
ListNode end = head;
while(end.next != null){
end = end.next;
}
return mergeSort(head, end);
}
public ListNode merge(ListNode left, ListNode right){
ListNode head = left.val > right.val ? right : left;
if(left.val > right.val){
right = right.next;
}else
left = left.next;
ListNode tmp = head;
while(left != null || right != null){
if(left == null || (right != null && left.val > right.val)){
tmp.next = right;
right = right.next;
tmp = tmp.next;
}else{
tmp.next = left;
left = left.next;
tmp = tmp.next;
}
}
return head;
}
public ListNode mergeSort(ListNode head, ListNode end){
if(head == end)
return head;
else{
int count = 0;
ListNode tmp = head;
while(tmp != end){
count++;
tmp = tmp.next;
}
int mid = count / 2;
ListNode mid_node = head;
while(mid != 0){//找中间点,然后分割左右。原归并排序中左边的长度也是大于等于右边
mid_node = mid_node.next;
mid--;
}
ListNode next_head = mid_node.next;
mid_node.next = null;//这一步很重要,分割左右,最终其实就是把一个链表在递归最底层变成了一个个独立的节点,再归并起来。
ListNode left = mergeSort(head, mid_node);
ListNode right = mergeSort(next_head, end);
return merge(left, right);
}
本文介绍了一种在链表上实现归并排序的方法,该方法能在O(nlogn)的时间复杂度内完成排序,并且仅使用常数级别的额外空间。文章详细解释了为何归并排序在这种情况下比快速排序更合适,并提供了具体的实现代码。
1951

被折叠的 条评论
为什么被折叠?



