148. Sort List
Leetcode Linked List SortSort a linked list in O(n \log n) time using constant space complexity.
Example 1:
Input: 4->2->1->3
Output: 1->2->3->4
Example 2:
Input: -1->5->3->4->0
Output: -1->0->3->4->5
分析¶
排序链表,要求空间复杂度为O(1),时间复杂度为O(n\log n)。既然要排序,很容易想到常见的排序方法,符合时间复杂度和空间复杂度的排序有快速排序和归并排序。
对于归并排序,把链表拆分成子链表,递归排序子链表,然后再合并链表。链表与数组不同,不能直接索引。为了表示子链表,一种方法是子链表在链表中的首尾位置,另一种更简单的方法是直接用子链表的首部位置表示,而将尾部位置设置为null。这是关键的一步,可以节省很多不必要的麻烦。链表的分割可参考LeetCode 21. Merge Two Sorted Lists,链表的中点可参考LeetCode 876. Middle of the Linked List。
public ListNode sortList(ListNode head) {
if (head == null || head.next == null) return head;
ListNode mid = midList(head);
ListNode right = sortList(mid.next); // sort right half
mid.next = null;
ListNode left = sortList(head); // sort left half
return merge(left, right);
}
// merge linkedlist l1 nad l2
private ListNode merge(ListNode left, ListNode right) {
// find head of the linkedlist after sorted
ListNode head, pos;
if (left.val < right.val) { head = left; left = left.next; }
else {head = right; right = right.next; }
pos = head;
// iterate the linked list, link smaller element to head
while (left != null && right != null) { // both iterate to the end of list
if (left.val < right.val) { pos.next = left; left = left.next;}
else {pos.next = right; right = right.next;}
pos = pos.next;
}
// add remaining linkedlist
pos.next = left != null ? left : right;
return head;
}
// find middle node of given linkedlist
private ListNode midList(ListNode head) {
ListNode slow = head, fast = head.next;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
使用快速排序,比归并排序复杂,原因至少有以下几点
- 无法访问链表的前一个节点,也就意味着传统的partition方法在这里不适用,在partition时,只能将小于pivot的节点插入到pivot前面,将大于pivot的节点插入到pivot后面。
- 传统的快速排序通过shuffle来保证平均时间复杂度,这里也不能适用。
- 插入元素的时候,不能适用dummy node,因为空间复杂度必须为O(1),论坛上好多快速排序的答案都不符合要求。
public ListNode sortList(ListNode head) {
return quickSort(head, null);
}
public ListNode quickSort(ListNode begin, ListNode end) {
// quit condition: only one node
if (begin == end) return begin;
ListNode head = partition(begin, end); // partition, result: head .... begin...end
ListNode h1 = quickSort(head, begin); // sort left part, result: h1... head....
ListNode h2 = quickSort(begin.next, end); // sort right part, result: h2... begin.next...
begin.next = h2;
return h1;
}
// use begin as pivot:
// left to the begin is less than pivot, right to the begin is larger than pivot
ListNode partition(ListNode begin, ListNode end) {
// head is the head, cur is currently visiting node, pre is the node before cur.
ListNode head = begin, pre = head, cur = pre.next;
int pivotValue = head.val; //pivot value
while (cur != end) {
// insert current node before head if it's value is less than pivot
if (cur != null && cur.val < pivotValue) {
pre.next = cur.next;
cur.next = head;
head = cur;
} else {
pre = cur;
}
cur = pre.next;
}
return head;
}