Sort a linked list in O(n log n) time using constant space complexity.
這題思路比較直接。按照題目要求,需要在O(N*logN)時間內完成排序,所以可以考慮的排序方法只有三個:快速排序、歸併排序和堆排序。然而,快速排序要求能在常數時間內置換兩個指定元素,所以需要支持O(1)時間內的隨機訪問,這個無法在鏈表上實現,所以可以排除快速排序。此外,堆排序需要O(N)的額外空間進行堆的建立,而題目要求常數的空間複雜度,所以也可以排除。如此一來,只有歸併排序可以考慮了。
按照分治法原理,其實也就只用實現兩個步驟,即拆分鏈表和合並鏈表,都可以通過鏈表實現。把一個鏈表拆分成兩半,簡單做法就是先掃描一遍獲得鏈表長度,然後再掃描一半長度進行拆分。這樣需要兩次掃描,雖然時間複雜度一樣。更有效的方法是使用2個快慢指針,當快指針走到頭的時候,慢指針會到達第一個的子鏈表的尾部。合併鏈表很直接,因爲無論是用數組還是鏈表,都只用順序掃描兩個序列。
// merge the two sorted sublists
private ListNode merge(ListNode head1, ListNode head2) {
if (head1 == null) {
return head2;
} else if (head2 == null) {
return head1;
}
ListNode head, prev;
if (head1.val < head2.val) {
head = head1;
head1 = head1.next;
} else {
head = head2;
head2 = head2.next;
}
prev = head;
while (head1 != null && head2 != null) {
if (head1.val < head2.val) {
prev.next = head1;
head1 = head1.next;
} else {
prev.next = head2;
head2 = head2.next;
}
prev = prev.next;
}
if (head1 != null) {
prev.next = head1;
} else if (head2 != null) {
prev.next = head2;
}
return head;
}
public ListNode sortList(ListNode head) {
ListNode head1, head2; // sublist heads
// split the list into two sublists
if (head == null || head.next == null) {
return head;
} else {
ListNode fastNode = head.next, slowNode = head;
while (fastNode != null) {
fastNode = fastNode.next;
if (fastNode != null) {
fastNode = fastNode.next;
slowNode = slowNode.next;
}
}
head1 = head;
head2 = slowNode.next;
slowNode.next = null;
}
// sort the two sublists recursively
head1 = sortList(head1);
head2 = sortList(head2);
return merge(head1, head2);
}
其實我本來想把拆分鏈表這一步也用一個函數封裝的,不過這樣的話要求對兩個子鏈表的表頭地址傳引用,在Java裏不好實現,不過可以在C++代碼裏引入二級指針或者對節點地址傳引用實現。